NPS plays and pondering
[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((void));
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
1945    startedFromPositionFile = FALSE;
1946    if(gameInfo.variant == newVariant) return;
1947
1948    /* [HGM] This routine is called each time an assignment is made to
1949     * gameInfo.variant during a game, to make sure the board sizes
1950     * are set to match the new variant. If that means adding or deleting
1951     * holdings, we shift the playing board accordingly
1952     * This kludge is needed because in ICS observe mode, we get boards
1953     * of an ongoing game without knowing the variant, and learn about the
1954     * latter only later. This can be because of the move list we requested,
1955     * in which case the game history is refilled from the beginning anyway,
1956     * but also when receiving holdings of a crazyhouse game. In the latter
1957     * case we want to add those holdings to the already received position.
1958     */
1959
1960    
1961    if (appData.debugMode) {
1962      fprintf(debugFP, "Switch board from %s to %s\n",
1963              VariantName(gameInfo.variant), VariantName(newVariant));
1964      setbuf(debugFP, NULL);
1965    }
1966    shuffleOpenings = 0;       /* [HGM] shuffle */
1967    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1968    switch(newVariant) 
1969      {
1970      case VariantShogi:
1971        newWidth = 9;  newHeight = 9;
1972        gameInfo.holdingsSize = 7;
1973      case VariantBughouse:
1974      case VariantCrazyhouse:
1975        newHoldingsWidth = 2; break;
1976      case VariantGreat:
1977        newWidth = 10;
1978      case VariantSuper:
1979        newHoldingsWidth = 2;
1980        gameInfo.holdingsSize = 8;
1981        break;
1982      case VariantGothic:
1983      case VariantCapablanca:
1984      case VariantCapaRandom:
1985        newWidth = 10;
1986      default:
1987        newHoldingsWidth = gameInfo.holdingsSize = 0;
1988      };
1989    
1990    if(newWidth  != gameInfo.boardWidth  ||
1991       newHeight != gameInfo.boardHeight ||
1992       newHoldingsWidth != gameInfo.holdingsWidth ) {
1993      
1994      /* shift position to new playing area, if needed */
1995      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996        for(i=0; i<BOARD_HEIGHT; i++) 
1997          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999              board[i][j];
2000        for(i=0; i<newHeight; i++) {
2001          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003        }
2004      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005        for(i=0; i<BOARD_HEIGHT; i++)
2006          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008              board[i][j];
2009      }
2010      gameInfo.boardWidth  = newWidth;
2011      gameInfo.boardHeight = newHeight;
2012      gameInfo.holdingsWidth = newHoldingsWidth;
2013      gameInfo.variant = newVariant;
2014      InitDrawingSizes(-2, 0);
2015      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2016    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\   ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2031
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2034
2035 void
2036 read_from_ics(isr, closure, data, count, error)
2037      InputSourceRef isr;
2038      VOIDSTAR closure;
2039      char *data;
2040      int count;
2041      int error;
2042 {
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2052     
2053     static int started = STARTED_NONE;
2054     static char parse[20000];
2055     static int parse_pos = 0;
2056     static char buf[BUF_SIZE + 1];
2057     static int firstTime = TRUE, intfSet = FALSE;
2058     static ColorClass prevColor = ColorNormal;
2059     static int savingComment = FALSE;
2060     static int cmatch = 0; // continuation sequence match
2061     char *bp;
2062     char str[500];
2063     int i, oldi;
2064     int buf_len;
2065     int next_out;
2066     int tkind;
2067     int backup;    /* [DM] For zippy color lines */
2068     char *p;
2069     char talker[MSG_SIZ]; // [HGM] chat
2070     int channel;
2071
2072     if (appData.debugMode) {
2073       if (!error) {
2074         fprintf(debugFP, "<ICS: ");
2075         show_bytes(debugFP, data, count);
2076         fprintf(debugFP, "\n");
2077       }
2078     }
2079
2080     if (appData.debugMode) { int f = forwardMostMove;
2081         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2083     }
2084     if (count > 0) {
2085         /* If last read ended with a partial line that we couldn't parse,
2086            prepend it to the new read and try again. */
2087         if (leftover_len > 0) {
2088             for (i=0; i<leftover_len; i++)
2089               buf[i] = buf[leftover_start + i];
2090         }
2091
2092     /* copy new characters into the buffer */
2093     bp = buf + leftover_len;
2094     buf_len=leftover_len;
2095     for (i=0; i<count; i++)
2096     {
2097         // ignore these
2098         if (data[i] == '\r')
2099             continue;
2100
2101         // join lines split by ICS?
2102         if (!appData.noJoin)
2103         {
2104             /*
2105                 Joining just consists of finding matches against the
2106                 continuation sequence, and discarding that sequence
2107                 if found instead of copying it.  So, until a match
2108                 fails, there's nothing to do since it might be the
2109                 complete sequence, and thus, something we don't want
2110                 copied.
2111             */
2112             if (data[i] == cont_seq[cmatch])
2113             {
2114                 cmatch++;
2115                 if (cmatch == strlen(cont_seq))
2116                 {
2117                     cmatch = 0; // complete match.  just reset the counter
2118
2119                     /*
2120                         it's possible for the ICS to not include the space
2121                         at the end of the last word, making our [correct]
2122                         join operation fuse two separate words.  the server
2123                         does this when the space occurs at the width setting.
2124                     */
2125                     if (!buf_len || buf[buf_len-1] != ' ')
2126                     {
2127                         *bp++ = ' ';
2128                         buf_len++;
2129                     }
2130                 }
2131                 continue;
2132             }
2133             else if (cmatch)
2134             {
2135                 /*
2136                     match failed, so we have to copy what matched before
2137                     falling through and copying this character.  In reality,
2138                     this will only ever be just the newline character, but
2139                     it doesn't hurt to be precise.
2140                 */
2141                 strncpy(bp, cont_seq, cmatch);
2142                 bp += cmatch;
2143                 buf_len += cmatch;
2144                 cmatch = 0;
2145             }
2146         }
2147
2148         // copy this char
2149         *bp++ = data[i];
2150         buf_len++;
2151     }
2152
2153         buf[buf_len] = NULLCHAR;
2154         next_out = leftover_len;
2155         leftover_start = 0;
2156         
2157         i = 0;
2158         while (i < buf_len) {
2159             /* Deal with part of the TELNET option negotiation
2160                protocol.  We refuse to do anything beyond the
2161                defaults, except that we allow the WILL ECHO option,
2162                which ICS uses to turn off password echoing when we are
2163                directly connected to it.  We reject this option
2164                if localLineEditing mode is on (always on in xboard)
2165                and we are talking to port 23, which might be a real
2166                telnet server that will try to keep WILL ECHO on permanently.
2167              */
2168             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170                 unsigned char option;
2171                 oldi = i;
2172                 switch ((unsigned char) buf[++i]) {
2173                   case TN_WILL:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<WILL ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       case TN_ECHO:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "ECHO ");
2180                         /* Reply only if this is a change, according
2181                            to the protocol rules. */
2182                         if (remoteEchoOption) break;
2183                         if (appData.localLineEditing &&
2184                             atoi(appData.icsPort) == TN_PORT) {
2185                             TelnetRequest(TN_DONT, TN_ECHO);
2186                         } else {
2187                             EchoOff();
2188                             TelnetRequest(TN_DO, TN_ECHO);
2189                             remoteEchoOption = TRUE;
2190                         }
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         /* Whatever this is, we don't want it. */
2196                         TelnetRequest(TN_DONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_WONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (!remoteEchoOption) break;
2210                         EchoOn();
2211                         TelnetRequest(TN_DONT, TN_ECHO);
2212                         remoteEchoOption = FALSE;
2213                         break;
2214                       default:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", (unsigned char) option);
2217                         /* Whatever this is, it must already be turned
2218                            off, because we never agree to turn on
2219                            anything non-default, so according to the
2220                            protocol rules, we don't reply. */
2221                         break;
2222                     }
2223                     break;
2224                   case TN_DO:
2225                     if (appData.debugMode)
2226                       fprintf(debugFP, "\n<DO ");
2227                     switch (option = (unsigned char) buf[++i]) {
2228                       default:
2229                         /* Whatever this is, we refuse to do it. */
2230                         if (appData.debugMode)
2231                           fprintf(debugFP, "%d ", option);
2232                         TelnetRequest(TN_WONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_DONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<DONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         /* Whatever this is, we are already not doing
2244                            it, because we never agree to do anything
2245                            non-default, so according to the protocol
2246                            rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_IAC:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<IAC ");
2253                     /* Doubled IAC; pass it through */
2254                     i--;
2255                     break;
2256                   default:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259                     /* Drop all other telnet commands on the floor */
2260                     break;
2261                 }
2262                 if (oldi > next_out)
2263                   SendToPlayer(&buf[next_out], oldi - next_out);
2264                 if (++i > next_out)
2265                   next_out = i;
2266                 continue;
2267             }
2268                 
2269             /* OK, this at least will *usually* work */
2270             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2271                 loggedOn = TRUE;
2272             }
2273             
2274             if (loggedOn && !intfSet) {
2275                 if (ics_type == ICS_ICC) {
2276                   sprintf(str,
2277                           "/set-quietly interface %s\n/set-quietly style 12\n",
2278                           programVersion);
2279                 } else if (ics_type == ICS_CHESSNET) {
2280                   sprintf(str, "/style 12\n");
2281                 } else {
2282                   strcpy(str, "alias $ @\n$set interface ");
2283                   strcat(str, programVersion);
2284                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 #ifdef WIN32
2286                   strcat(str, "$iset nohighlight 1\n");
2287 #endif
2288                   strcat(str, "$iset lock 1\n$style 12\n");
2289                 }
2290                 SendToICS(str);
2291                 NotifyFrontendLogin();
2292                 intfSet = TRUE;
2293             }
2294
2295             if (started == STARTED_COMMENT) {
2296                 /* Accumulate characters in comment */
2297                 parse[parse_pos++] = buf[i];
2298                 if (buf[i] == '\n') {
2299                     parse[parse_pos] = NULLCHAR;
2300                     if(chattingPartner>=0) {
2301                         char mess[MSG_SIZ];
2302                         sprintf(mess, "%s%s", talker, parse);
2303                         OutputChatMessage(chattingPartner, mess);
2304                         chattingPartner = -1;
2305                     } else
2306                     if(!suppressKibitz) // [HGM] kibitz
2307                         AppendComment(forwardMostMove, StripHighlight(parse));
2308                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309                         int nrDigit = 0, nrAlph = 0, i;
2310                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312                         parse[parse_pos] = NULLCHAR;
2313                         // try to be smart: if it does not look like search info, it should go to
2314                         // ICS interaction window after all, not to engine-output window.
2315                         for(i=0; i<parse_pos; i++) { // count letters and digits
2316                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2318                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2319                         }
2320                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321                             int depth=0; float score;
2322                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324                                 pvInfoList[forwardMostMove-1].depth = depth;
2325                                 pvInfoList[forwardMostMove-1].score = 100*score;
2326                             }
2327                             OutputKibitz(suppressKibitz, parse);
2328                         } else {
2329                             char tmp[MSG_SIZ];
2330                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331                             SendToPlayer(tmp, strlen(tmp));
2332                         }
2333                     }
2334                     started = STARTED_NONE;
2335                 } else {
2336                     /* Don't match patterns against characters in chatter */
2337                     i++;
2338                     continue;
2339                 }
2340             }
2341             if (started == STARTED_CHATTER) {
2342                 if (buf[i] != '\n') {
2343                     /* Don't match patterns against characters in chatter */
2344                     i++;
2345                     continue;
2346                 }
2347                 started = STARTED_NONE;
2348             }
2349
2350             /* Kludge to deal with rcmd protocol */
2351             if (firstTime && looking_at(buf, &i, "\001*")) {
2352                 DisplayFatalError(&buf[1], 0, 1);
2353                 continue;
2354             } else {
2355                 firstTime = FALSE;
2356             }
2357
2358             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2359                 ics_type = ICS_ICC;
2360                 ics_prefix = "/";
2361                 if (appData.debugMode)
2362                   fprintf(debugFP, "ics_type %d\n", ics_type);
2363                 continue;
2364             }
2365             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366                 ics_type = ICS_FICS;
2367                 ics_prefix = "$";
2368                 if (appData.debugMode)
2369                   fprintf(debugFP, "ics_type %d\n", ics_type);
2370                 continue;
2371             }
2372             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373                 ics_type = ICS_CHESSNET;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379
2380             if (!loggedOn &&
2381                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2383                  looking_at(buf, &i, "will be \"*\""))) {
2384               strcpy(ics_handle, star_match[0]);
2385               continue;
2386             }
2387
2388             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389               char buf[MSG_SIZ];
2390               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391               DisplayIcsInteractionTitle(buf);
2392               have_set_title = TRUE;
2393             }
2394
2395             /* skip finger notes */
2396             if (started == STARTED_NONE &&
2397                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398                  (buf[i] == '1' && buf[i+1] == '0')) &&
2399                 buf[i+2] == ':' && buf[i+3] == ' ') {
2400               started = STARTED_CHATTER;
2401               i += 3;
2402               continue;
2403             }
2404
2405             /* skip formula vars */
2406             if (started == STARTED_NONE &&
2407                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408               started = STARTED_CHATTER;
2409               i += 3;
2410               continue;
2411             }
2412
2413             oldi = i;
2414             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415             if (appData.autoKibitz && started == STARTED_NONE && 
2416                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2417                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418                 if(looking_at(buf, &i, "* kibitzes: ") &&
2419                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2420                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2421                         suppressKibitz = TRUE;
2422                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423                                 && (gameMode == IcsPlayingWhite)) ||
2424                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2426                             started = STARTED_CHATTER; // own kibitz we simply discard
2427                         else {
2428                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429                             parse_pos = 0; parse[0] = NULLCHAR;
2430                             savingComment = TRUE;
2431                             suppressKibitz = gameMode != IcsObserving ? 2 :
2432                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2433                         } 
2434                         continue;
2435                 } else
2436                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437                     started = STARTED_CHATTER;
2438                     suppressKibitz = TRUE;
2439                 }
2440             } // [HGM] kibitz: end of patch
2441
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443
2444             // [HGM] chat: intercept tells by users for which we have an open chat window
2445             channel = -1;
2446             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2447                                            looking_at(buf, &i, "* whispers:") ||
2448                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450                 int p;
2451                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452                 chattingPartner = -1;
2453
2454                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455                 for(p=0; p<MAX_CHAT; p++) {
2456                     if(channel == atoi(chatPartner[p])) {
2457                     talker[0] = '['; strcat(talker, "]");
2458                     chattingPartner = p; break;
2459                     }
2460                 } else
2461                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462                 for(p=0; p<MAX_CHAT; p++) {
2463                     if(!strcmp("WHISPER", chatPartner[p])) {
2464                         talker[0] = '['; strcat(talker, "]");
2465                         chattingPartner = p; break;
2466                     }
2467                 }
2468                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470                     talker[0] = 0;
2471                     chattingPartner = p; break;
2472                 }
2473                 if(chattingPartner<0) i = oldi; else {
2474                     started = STARTED_COMMENT;
2475                     parse_pos = 0; parse[0] = NULLCHAR;
2476                     savingComment = TRUE;
2477                     suppressKibitz = TRUE;
2478                 }
2479             } // [HGM] chat: end of patch
2480
2481             if (appData.zippyTalk || appData.zippyPlay) {
2482                 /* [DM] Backup address for color zippy lines */
2483                 backup = i;
2484 #if ZIPPY
2485        #ifdef WIN32
2486                if (loggedOn == TRUE)
2487                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489        #else
2490                 if (ZippyControl(buf, &i) ||
2491                     ZippyConverse(buf, &i) ||
2492                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493                       loggedOn = TRUE;
2494                       if (!appData.colorize) continue;
2495                 }
2496        #endif
2497 #endif
2498             } // [DM] 'else { ' deleted
2499                 if (
2500                     /* Regular tells and says */
2501                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2503                     looking_at(buf, &i, "* says: ") ||
2504                     /* Don't color "message" or "messages" output */
2505                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506                     looking_at(buf, &i, "*. * at *:*: ") ||
2507                     looking_at(buf, &i, "--* (*:*): ") ||
2508                     /* Message notifications (same color as tells) */
2509                     looking_at(buf, &i, "* has left a message ") ||
2510                     looking_at(buf, &i, "* just sent you a message:\n") ||
2511                     /* Whispers and kibitzes */
2512                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513                     looking_at(buf, &i, "* kibitzes: ") ||
2514                     /* Channel tells */
2515                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516
2517                   if (tkind == 1 && strchr(star_match[0], ':')) {
2518                       /* Avoid "tells you:" spoofs in channels */
2519                      tkind = 3;
2520                   }
2521                   if (star_match[0][0] == NULLCHAR ||
2522                       strchr(star_match[0], ' ') ||
2523                       (tkind == 3 && strchr(star_match[1], ' '))) {
2524                     /* Reject bogus matches */
2525                     i = oldi;
2526                   } else {
2527                     if (appData.colorize) {
2528                       if (oldi > next_out) {
2529                         SendToPlayer(&buf[next_out], oldi - next_out);
2530                         next_out = oldi;
2531                       }
2532                       switch (tkind) {
2533                       case 1:
2534                         Colorize(ColorTell, FALSE);
2535                         curColor = ColorTell;
2536                         break;
2537                       case 2:
2538                         Colorize(ColorKibitz, FALSE);
2539                         curColor = ColorKibitz;
2540                         break;
2541                       case 3:
2542                         p = strrchr(star_match[1], '(');
2543                         if (p == NULL) {
2544                           p = star_match[1];
2545                         } else {
2546                           p++;
2547                         }
2548                         if (atoi(p) == 1) {
2549                           Colorize(ColorChannel1, FALSE);
2550                           curColor = ColorChannel1;
2551                         } else {
2552                           Colorize(ColorChannel, FALSE);
2553                           curColor = ColorChannel;
2554                         }
2555                         break;
2556                       case 5:
2557                         curColor = ColorNormal;
2558                         break;
2559                       }
2560                     }
2561                     if (started == STARTED_NONE && appData.autoComment &&
2562                         (gameMode == IcsObserving ||
2563                          gameMode == IcsPlayingWhite ||
2564                          gameMode == IcsPlayingBlack)) {
2565                       parse_pos = i - oldi;
2566                       memcpy(parse, &buf[oldi], parse_pos);
2567                       parse[parse_pos] = NULLCHAR;
2568                       started = STARTED_COMMENT;
2569                       savingComment = TRUE;
2570                     } else {
2571                       started = STARTED_CHATTER;
2572                       savingComment = FALSE;
2573                     }
2574                     loggedOn = TRUE;
2575                     continue;
2576                   }
2577                 }
2578
2579                 if (looking_at(buf, &i, "* s-shouts: ") ||
2580                     looking_at(buf, &i, "* c-shouts: ")) {
2581                     if (appData.colorize) {
2582                         if (oldi > next_out) {
2583                             SendToPlayer(&buf[next_out], oldi - next_out);
2584                             next_out = oldi;
2585                         }
2586                         Colorize(ColorSShout, FALSE);
2587                         curColor = ColorSShout;
2588                     }
2589                     loggedOn = TRUE;
2590                     started = STARTED_CHATTER;
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "--->")) {
2595                     loggedOn = TRUE;
2596                     continue;
2597                 }
2598
2599                 if (looking_at(buf, &i, "* shouts: ") ||
2600                     looking_at(buf, &i, "--> ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorShout, FALSE);
2607                         curColor = ColorShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at( buf, &i, "Challenge:")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorChallenge, FALSE);
2621                         curColor = ColorChallenge;
2622                     }
2623                     loggedOn = TRUE;
2624                     continue;
2625                 }
2626
2627                 if (looking_at(buf, &i, "* offers you") ||
2628                     looking_at(buf, &i, "* offers to be") ||
2629                     looking_at(buf, &i, "* would like to") ||
2630                     looking_at(buf, &i, "* requests to") ||
2631                     looking_at(buf, &i, "Your opponent offers") ||
2632                     looking_at(buf, &i, "Your opponent requests")) {
2633
2634                     if (appData.colorize) {
2635                         if (oldi > next_out) {
2636                             SendToPlayer(&buf[next_out], oldi - next_out);
2637                             next_out = oldi;
2638                         }
2639                         Colorize(ColorRequest, FALSE);
2640                         curColor = ColorRequest;
2641                     }
2642                     continue;
2643                 }
2644
2645                 if (looking_at(buf, &i, "* (*) seeking")) {
2646                     if (appData.colorize) {
2647                         if (oldi > next_out) {
2648                             SendToPlayer(&buf[next_out], oldi - next_out);
2649                             next_out = oldi;
2650                         }
2651                         Colorize(ColorSeek, FALSE);
2652                         curColor = ColorSeek;
2653                     }
2654                     continue;
2655             }
2656
2657             if (looking_at(buf, &i, "\\   ")) {
2658                 if (prevColor != ColorNormal) {
2659                     if (oldi > next_out) {
2660                         SendToPlayer(&buf[next_out], oldi - next_out);
2661                         next_out = oldi;
2662                     }
2663                     Colorize(prevColor, TRUE);
2664                     curColor = prevColor;
2665                 }
2666                 if (savingComment) {
2667                     parse_pos = i - oldi;
2668                     memcpy(parse, &buf[oldi], parse_pos);
2669                     parse[parse_pos] = NULLCHAR;
2670                     started = STARTED_COMMENT;
2671                 } else {
2672                     started = STARTED_CHATTER;
2673                 }
2674                 continue;
2675             }
2676
2677             if (looking_at(buf, &i, "Black Strength :") ||
2678                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679                 looking_at(buf, &i, "<10>") ||
2680                 looking_at(buf, &i, "#@#")) {
2681                 /* Wrong board style */
2682                 loggedOn = TRUE;
2683                 SendToICS(ics_prefix);
2684                 SendToICS("set style 12\n");
2685                 SendToICS(ics_prefix);
2686                 SendToICS("refresh\n");
2687                 continue;
2688             }
2689             
2690             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691                 ICSInitScript();
2692                 have_sent_ICS_logon = 1;
2693                 continue;
2694             }
2695               
2696             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2697                 (looking_at(buf, &i, "\n<12> ") ||
2698                  looking_at(buf, &i, "<12> "))) {
2699                 loggedOn = TRUE;
2700                 if (oldi > next_out) {
2701                     SendToPlayer(&buf[next_out], oldi - next_out);
2702                 }
2703                 next_out = i;
2704                 started = STARTED_BOARD;
2705                 parse_pos = 0;
2706                 continue;
2707             }
2708
2709             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710                 looking_at(buf, &i, "<b1> ")) {
2711                 if (oldi > next_out) {
2712                     SendToPlayer(&buf[next_out], oldi - next_out);
2713                 }
2714                 next_out = i;
2715                 started = STARTED_HOLDINGS;
2716                 parse_pos = 0;
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721                 loggedOn = TRUE;
2722                 /* Header for a move list -- first line */
2723
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     switch (gameMode) {
2727                       case IcsIdle:
2728                       case BeginningOfGame:
2729                         /* User typed "moves" or "oldmoves" while we
2730                            were idle.  Pretend we asked for these
2731                            moves and soak them up so user can step
2732                            through them and/or save them.
2733                            */
2734                         Reset(FALSE, TRUE);
2735                         gameMode = IcsObserving;
2736                         ModeHighlight();
2737                         ics_gamenum = -1;
2738                         ics_getting_history = H_GOT_UNREQ_HEADER;
2739                         break;
2740                       case EditGame: /*?*/
2741                       case EditPosition: /*?*/
2742                         /* Should above feature work in these modes too? */
2743                         /* For now it doesn't */
2744                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2745                         break;
2746                       default:
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                     }
2750                     break;
2751                   case H_REQUESTED:
2752                     /* Is this the right one? */
2753                     if (gameInfo.white && gameInfo.black &&
2754                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2755                         strcmp(gameInfo.black, star_match[2]) == 0) {
2756                         /* All is well */
2757                         ics_getting_history = H_GOT_REQ_HEADER;
2758                     }
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                   case H_GOT_UNREQ_HEADER:
2762                   case H_GOT_UNWANTED_HEADER:
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: two headers"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                 }
2769
2770                 /* Save player ratings into gameInfo if needed */
2771                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773                     (gameInfo.whiteRating == -1 ||
2774                      gameInfo.blackRating == -1)) {
2775
2776                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2777                     gameInfo.blackRating = string_to_rating(star_match[3]);
2778                     if (appData.debugMode)
2779                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2780                               gameInfo.whiteRating, gameInfo.blackRating);
2781                 }
2782                 continue;
2783             }
2784
2785             if (looking_at(buf, &i,
2786               "* * match, initial time: * minute*, increment: * second")) {
2787                 /* Header for a move list -- second line */
2788                 /* Initial board will follow if this is a wild game */
2789                 if (gameInfo.event != NULL) free(gameInfo.event);
2790                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791                 gameInfo.event = StrSave(str);
2792                 /* [HGM] we switched variant. Translate boards if needed. */
2793                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i, "Move  ")) {
2798                 /* Beginning of a move list */
2799                 switch (ics_getting_history) {
2800                   case H_FALSE:
2801                     /* Normally should not happen */
2802                     /* Maybe user hit reset while we were parsing */
2803                     break;
2804                   case H_REQUESTED:
2805                     /* Happens if we are ignoring a move list that is not
2806                      * the one we just requested.  Common if the user
2807                      * tries to observe two games without turning off
2808                      * getMoveList */
2809                     break;
2810                   case H_GETTING_MOVES:
2811                     /* Should not happen */
2812                     DisplayError(_("Error gathering move list: nested"), 0);
2813                     ics_getting_history = H_FALSE;
2814                     break;
2815                   case H_GOT_REQ_HEADER:
2816                     ics_getting_history = H_GETTING_MOVES;
2817                     started = STARTED_MOVES;
2818                     parse_pos = 0;
2819                     if (oldi > next_out) {
2820                         SendToPlayer(&buf[next_out], oldi - next_out);
2821                     }
2822                     break;
2823                   case H_GOT_UNREQ_HEADER:
2824                     ics_getting_history = H_GETTING_MOVES;
2825                     started = STARTED_MOVES_NOHIDE;
2826                     parse_pos = 0;
2827                     break;
2828                   case H_GOT_UNWANTED_HEADER:
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                 }
2832                 continue;
2833             }                           
2834             
2835             if (looking_at(buf, &i, "% ") ||
2836                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838                 savingComment = FALSE;
2839                 switch (started) {
2840                   case STARTED_MOVES:
2841                   case STARTED_MOVES_NOHIDE:
2842                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843                     parse[parse_pos + i - oldi] = NULLCHAR;
2844                     ParseGameHistory(parse);
2845 #if ZIPPY
2846                     if (appData.zippyPlay && first.initDone) {
2847                         FeedMovesToProgram(&first, forwardMostMove);
2848                         if (gameMode == IcsPlayingWhite) {
2849                             if (WhiteOnMove(forwardMostMove)) {
2850                                 if (first.sendTime) {
2851                                   if (first.useColors) {
2852                                     SendToProgram("black\n", &first); 
2853                                   }
2854                                   SendTimeRemaining(&first, TRUE);
2855                                 }
2856                                 if (first.useColors) {
2857                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858                                 }
2859                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860                                 first.maybeThinking = TRUE;
2861                             } else {
2862                                 if (first.usePlayother) {
2863                                   if (first.sendTime) {
2864                                     SendTimeRemaining(&first, TRUE);
2865                                   }
2866                                   SendToProgram("playother\n", &first);
2867                                   firstMove = FALSE;
2868                                 } else {
2869                                   firstMove = TRUE;
2870                                 }
2871                             }
2872                         } else if (gameMode == IcsPlayingBlack) {
2873                             if (!WhiteOnMove(forwardMostMove)) {
2874                                 if (first.sendTime) {
2875                                   if (first.useColors) {
2876                                     SendToProgram("white\n", &first);
2877                                   }
2878                                   SendTimeRemaining(&first, FALSE);
2879                                 }
2880                                 if (first.useColors) {
2881                                   SendToProgram("black\n", &first);
2882                                 }
2883                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884                                 first.maybeThinking = TRUE;
2885                             } else {
2886                                 if (first.usePlayother) {
2887                                   if (first.sendTime) {
2888                                     SendTimeRemaining(&first, FALSE);
2889                                   }
2890                                   SendToProgram("playother\n", &first);
2891                                   firstMove = FALSE;
2892                                 } else {
2893                                   firstMove = TRUE;
2894                                 }
2895                             }
2896                         }                       
2897                     }
2898 #endif
2899                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2900                         /* Moves came from oldmoves or moves command
2901                            while we weren't doing anything else.
2902                            */
2903                         currentMove = forwardMostMove;
2904                         ClearHighlights();/*!!could figure this out*/
2905                         flipView = appData.flipView;
2906                         DrawPosition(TRUE, boards[currentMove]);
2907                         DisplayBothClocks();
2908                         sprintf(str, "%s vs. %s",
2909                                 gameInfo.white, gameInfo.black);
2910                         DisplayTitle(str);
2911                         gameMode = IcsIdle;
2912                     } else {
2913                         /* Moves were history of an active game */
2914                         if (gameInfo.resultDetails != NULL) {
2915                             free(gameInfo.resultDetails);
2916                             gameInfo.resultDetails = NULL;
2917                         }
2918                     }
2919                     HistorySet(parseList, backwardMostMove,
2920                                forwardMostMove, currentMove-1);
2921                     DisplayMove(currentMove - 1);
2922                     if (started == STARTED_MOVES) next_out = i;
2923                     started = STARTED_NONE;
2924                     ics_getting_history = H_FALSE;
2925                     break;
2926
2927                   case STARTED_OBSERVE:
2928                     started = STARTED_NONE;
2929                     SendToICS(ics_prefix);
2930                     SendToICS("refresh\n");
2931                     break;
2932
2933                   default:
2934                     break;
2935                 }
2936                 if(bookHit) { // [HGM] book: simulate book reply
2937                     static char bookMove[MSG_SIZ]; // a bit generous?
2938
2939                     programStats.nodes = programStats.depth = programStats.time = 
2940                     programStats.score = programStats.got_only_move = 0;
2941                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942
2943                     strcpy(bookMove, "move ");
2944                     strcat(bookMove, bookHit);
2945                     HandleMachineMove(bookMove, &first);
2946                 }
2947                 continue;
2948             }
2949             
2950             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951                  started == STARTED_HOLDINGS ||
2952                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953                 /* Accumulate characters in move list or board */
2954                 parse[parse_pos++] = buf[i];
2955             }
2956             
2957             /* Start of game messages.  Mostly we detect start of game
2958                when the first board image arrives.  On some versions
2959                of the ICS, though, we need to do a "refresh" after starting
2960                to observe in order to get the current board right away. */
2961             if (looking_at(buf, &i, "Adding game * to observation list")) {
2962                 started = STARTED_OBSERVE;
2963                 continue;
2964             }
2965
2966             /* Handle auto-observe */
2967             if (appData.autoObserve &&
2968                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970                 char *player;
2971                 /* Choose the player that was highlighted, if any. */
2972                 if (star_match[0][0] == '\033' ||
2973                     star_match[1][0] != '\033') {
2974                     player = star_match[0];
2975                 } else {
2976                     player = star_match[2];
2977                 }
2978                 sprintf(str, "%sobserve %s\n",
2979                         ics_prefix, StripHighlightAndTitle(player));
2980                 SendToICS(str);
2981
2982                 /* Save ratings from notify string */
2983                 strcpy(player1Name, star_match[0]);
2984                 player1Rating = string_to_rating(star_match[1]);
2985                 strcpy(player2Name, star_match[2]);
2986                 player2Rating = string_to_rating(star_match[3]);
2987
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, 
2990                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2991                           player1Name, player1Rating,
2992                           player2Name, player2Rating);
2993
2994                 continue;
2995             }
2996
2997             /* Deal with automatic examine mode after a game,
2998                and with IcsObserving -> IcsExamining transition */
2999             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000                 looking_at(buf, &i, "has made you an examiner of game *")) {
3001
3002                 int gamenum = atoi(star_match[0]);
3003                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004                     gamenum == ics_gamenum) {
3005                     /* We were already playing or observing this game;
3006                        no need to refetch history */
3007                     gameMode = IcsExamining;
3008                     if (pausing) {
3009                         pauseExamForwardMostMove = forwardMostMove;
3010                     } else if (currentMove < forwardMostMove) {
3011                         ForwardInner(forwardMostMove);
3012                     }
3013                 } else {
3014                     /* I don't think this case really can happen */
3015                     SendToICS(ics_prefix);
3016                     SendToICS("refresh\n");
3017                 }
3018                 continue;
3019             }    
3020             
3021             /* Error messages */
3022 //          if (ics_user_moved) {
3023             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024                 if (looking_at(buf, &i, "Illegal move") ||
3025                     looking_at(buf, &i, "Not a legal move") ||
3026                     looking_at(buf, &i, "Your king is in check") ||
3027                     looking_at(buf, &i, "It isn't your turn") ||
3028                     looking_at(buf, &i, "It is not your move")) {
3029                     /* Illegal move */
3030                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031                         currentMove = --forwardMostMove;
3032                         DisplayMove(currentMove - 1); /* before DMError */
3033                         DrawPosition(FALSE, boards[currentMove]);
3034                         SwitchClocks();
3035                         DisplayBothClocks();
3036                     }
3037                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038                     ics_user_moved = 0;
3039                     continue;
3040                 }
3041             }
3042
3043             if (looking_at(buf, &i, "still have time") ||
3044                 looking_at(buf, &i, "not out of time") ||
3045                 looking_at(buf, &i, "either player is out of time") ||
3046                 looking_at(buf, &i, "has timeseal; checking")) {
3047                 /* We must have called his flag a little too soon */
3048                 whiteFlag = blackFlag = FALSE;
3049                 continue;
3050             }
3051
3052             if (looking_at(buf, &i, "added * seconds to") ||
3053                 looking_at(buf, &i, "seconds were added to")) {
3054                 /* Update the clocks */
3055                 SendToICS(ics_prefix);
3056                 SendToICS("refresh\n");
3057                 continue;
3058             }
3059
3060             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061                 ics_clock_paused = TRUE;
3062                 StopClocks();
3063                 continue;
3064             }
3065
3066             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067                 ics_clock_paused = FALSE;
3068                 StartClocks();
3069                 continue;
3070             }
3071
3072             /* Grab player ratings from the Creating: message.
3073                Note we have to check for the special case when
3074                the ICS inserts things like [white] or [black]. */
3075             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077                 /* star_matches:
3078                    0    player 1 name (not necessarily white)
3079                    1    player 1 rating
3080                    2    empty, white, or black (IGNORED)
3081                    3    player 2 name (not necessarily black)
3082                    4    player 2 rating
3083                    
3084                    The names/ratings are sorted out when the game
3085                    actually starts (below).
3086                 */
3087                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088                 player1Rating = string_to_rating(star_match[1]);
3089                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090                 player2Rating = string_to_rating(star_match[4]);
3091
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, 
3094                           "Ratings from 'Creating:' %s %d, %s %d\n",
3095                           player1Name, player1Rating,
3096                           player2Name, player2Rating);
3097
3098                 continue;
3099             }
3100             
3101             /* Improved generic start/end-of-game messages */
3102             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104                 /* If tkind == 0: */
3105                 /* star_match[0] is the game number */
3106                 /*           [1] is the white player's name */
3107                 /*           [2] is the black player's name */
3108                 /* For end-of-game: */
3109                 /*           [3] is the reason for the game end */
3110                 /*           [4] is a PGN end game-token, preceded by " " */
3111                 /* For start-of-game: */
3112                 /*           [3] begins with "Creating" or "Continuing" */
3113                 /*           [4] is " *" or empty (don't care). */
3114                 int gamenum = atoi(star_match[0]);
3115                 char *whitename, *blackname, *why, *endtoken;
3116                 ChessMove endtype = (ChessMove) 0;
3117
3118                 if (tkind == 0) {
3119                   whitename = star_match[1];
3120                   blackname = star_match[2];
3121                   why = star_match[3];
3122                   endtoken = star_match[4];
3123                 } else {
3124                   whitename = star_match[1];
3125                   blackname = star_match[3];
3126                   why = star_match[5];
3127                   endtoken = star_match[6];
3128                 }
3129
3130                 /* Game start messages */
3131                 if (strncmp(why, "Creating ", 9) == 0 ||
3132                     strncmp(why, "Continuing ", 11) == 0) {
3133                     gs_gamenum = gamenum;
3134                     strcpy(gs_kind, strchr(why, ' ') + 1);
3135 #if ZIPPY
3136                     if (appData.zippyPlay) {
3137                         ZippyGameStart(whitename, blackname);
3138                     }
3139 #endif /*ZIPPY*/
3140                     continue;
3141                 }
3142
3143                 /* Game end messages */
3144                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145                     ics_gamenum != gamenum) {
3146                     continue;
3147                 }
3148                 while (endtoken[0] == ' ') endtoken++;
3149                 switch (endtoken[0]) {
3150                   case '*':
3151                   default:
3152                     endtype = GameUnfinished;
3153                     break;
3154                   case '0':
3155                     endtype = BlackWins;
3156                     break;
3157                   case '1':
3158                     if (endtoken[1] == '/')
3159                       endtype = GameIsDrawn;
3160                     else
3161                       endtype = WhiteWins;
3162                     break;
3163                 }
3164                 GameEnds(endtype, why, GE_ICS);
3165 #if ZIPPY
3166                 if (appData.zippyPlay && first.initDone) {
3167                     ZippyGameEnd(endtype, why);
3168                     if (first.pr == NULL) {
3169                       /* Start the next process early so that we'll
3170                          be ready for the next challenge */
3171                       StartChessProgram(&first);
3172                     }
3173                     /* Send "new" early, in case this command takes
3174                        a long time to finish, so that we'll be ready
3175                        for the next challenge. */
3176                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177                     Reset(TRUE, TRUE);
3178                 }
3179 #endif /*ZIPPY*/
3180                 continue;
3181             }
3182
3183             if (looking_at(buf, &i, "Removing game * from observation") ||
3184                 looking_at(buf, &i, "no longer observing game *") ||
3185                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186                 if (gameMode == IcsObserving &&
3187                     atoi(star_match[0]) == ics_gamenum)
3188                   {
3189                       /* icsEngineAnalyze */
3190                       if (appData.icsEngineAnalyze) {
3191                             ExitAnalyzeMode();
3192                             ModeHighlight();
3193                       }
3194                       StopClocks();
3195                       gameMode = IcsIdle;
3196                       ics_gamenum = -1;
3197                       ics_user_moved = FALSE;
3198                   }
3199                 continue;
3200             }
3201
3202             if (looking_at(buf, &i, "no longer examining game *")) {
3203                 if (gameMode == IcsExamining &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             /* Advance leftover_start past any newlines we find,
3214                so only partial lines can get reparsed */
3215             if (looking_at(buf, &i, "\n")) {
3216                 prevColor = curColor;
3217                 if (curColor != ColorNormal) {
3218                     if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                     }
3222                     Colorize(ColorNormal, FALSE);
3223                     curColor = ColorNormal;
3224                 }
3225                 if (started == STARTED_BOARD) {
3226                     started = STARTED_NONE;
3227                     parse[parse_pos] = NULLCHAR;
3228                     ParseBoard12(parse);
3229                     ics_user_moved = 0;
3230
3231                     /* Send premove here */
3232                     if (appData.premove) {
3233                       char str[MSG_SIZ];
3234                       if (currentMove == 0 &&
3235                           gameMode == IcsPlayingWhite &&
3236                           appData.premoveWhite) {
3237                         sprintf(str, "%s\n", appData.premoveWhiteText);
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                         SendToICS(str);
3241                       } else if (currentMove == 1 &&
3242                                  gameMode == IcsPlayingBlack &&
3243                                  appData.premoveBlack) {
3244                         sprintf(str, "%s\n", appData.premoveBlackText);
3245                         if (appData.debugMode)
3246                           fprintf(debugFP, "Sending premove:\n");
3247                         SendToICS(str);
3248                       } else if (gotPremove) {
3249                         gotPremove = 0;
3250                         ClearPremoveHighlights();
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                           UserMoveEvent(premoveFromX, premoveFromY, 
3254                                         premoveToX, premoveToY, 
3255                                         premovePromoChar);
3256                       }
3257                     }
3258
3259                     /* Usually suppress following prompt */
3260                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261                         if (looking_at(buf, &i, "*% ")) {
3262                             savingComment = FALSE;
3263                         }
3264                     }
3265                     next_out = i;
3266                 } else if (started == STARTED_HOLDINGS) {
3267                     int gamenum;
3268                     char new_piece[MSG_SIZ];
3269                     started = STARTED_NONE;
3270                     parse[parse_pos] = NULLCHAR;
3271                     if (appData.debugMode)
3272                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3273                                                         parse, currentMove);
3274                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3275                         gamenum == ics_gamenum) {
3276                         if (gameInfo.variant == VariantNormal) {
3277                           /* [HGM] We seem to switch variant during a game!
3278                            * Presumably no holdings were displayed, so we have
3279                            * to move the position two files to the right to
3280                            * create room for them!
3281                            */
3282                           VariantClass newVariant;
3283                           switch(gameInfo.boardWidth) { // base guess on board width
3284                                 case 9:  newVariant = VariantShogi; break;
3285                                 case 10: newVariant = VariantGreat; break;
3286                                 default: newVariant = VariantCrazyhouse; break;
3287                           }
3288                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3289                           /* Get a move list just to see the header, which
3290                              will tell us whether this is really bug or zh */
3291                           if (ics_getting_history == H_FALSE) {
3292                             ics_getting_history = H_REQUESTED;
3293                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3294                             SendToICS(str);
3295                           }
3296                         }
3297                         new_piece[0] = NULLCHAR;
3298                         sscanf(parse, "game %d white [%s black [%s <- %s",
3299                                &gamenum, white_holding, black_holding,
3300                                new_piece);
3301                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3302                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3303                         /* [HGM] copy holdings to board holdings area */
3304                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3305                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3306                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3307 #if ZIPPY
3308                         if (appData.zippyPlay && first.initDone) {
3309                             ZippyHoldings(white_holding, black_holding,
3310                                           new_piece);
3311                         }
3312 #endif /*ZIPPY*/
3313                         if (tinyLayout || smallLayout) {
3314                             char wh[16], bh[16];
3315                             PackHolding(wh, white_holding);
3316                             PackHolding(bh, black_holding);
3317                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3318                                     gameInfo.white, gameInfo.black);
3319                         } else {
3320                             sprintf(str, "%s [%s] vs. %s [%s]",
3321                                     gameInfo.white, white_holding,
3322                                     gameInfo.black, black_holding);
3323                         }
3324
3325                         DrawPosition(FALSE, boards[currentMove]);
3326                         DisplayTitle(str);
3327                     }
3328                     /* Suppress following prompt */
3329                     if (looking_at(buf, &i, "*% ")) {
3330                         savingComment = FALSE;
3331                     }
3332                     next_out = i;
3333                 }
3334                 continue;
3335             }
3336
3337             i++;                /* skip unparsed character and loop back */
3338         }
3339         
3340         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3341             started != STARTED_HOLDINGS && i > next_out) {
3342             SendToPlayer(&buf[next_out], i - next_out);
3343             next_out = i;
3344         }
3345         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3346         
3347         leftover_len = buf_len - leftover_start;
3348         /* if buffer ends with something we couldn't parse,
3349            reparse it after appending the next read */
3350         
3351     } else if (count == 0) {
3352         RemoveInputSource(isr);
3353         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3354     } else {
3355         DisplayFatalError(_("Error reading from ICS"), error, 1);
3356     }
3357 }
3358
3359
3360 /* Board style 12 looks like this:
3361    
3362    <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
3363    
3364  * The "<12> " is stripped before it gets to this routine.  The two
3365  * trailing 0's (flip state and clock ticking) are later addition, and
3366  * some chess servers may not have them, or may have only the first.
3367  * Additional trailing fields may be added in the future.  
3368  */
3369
3370 #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"
3371
3372 #define RELATION_OBSERVING_PLAYED    0
3373 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3374 #define RELATION_PLAYING_MYMOVE      1
3375 #define RELATION_PLAYING_NOTMYMOVE  -1
3376 #define RELATION_EXAMINING           2
3377 #define RELATION_ISOLATED_BOARD     -3
3378 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3379
3380 void
3381 ParseBoard12(string)
3382      char *string;
3383
3384     GameMode newGameMode;
3385     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3386     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3387     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3388     char to_play, board_chars[200];
3389     char move_str[500], str[500], elapsed_time[500];
3390     char black[32], white[32];
3391     Board board;
3392     int prevMove = currentMove;
3393     int ticking = 2;
3394     ChessMove moveType;
3395     int fromX, fromY, toX, toY;
3396     char promoChar;
3397     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3398     char *bookHit = NULL; // [HGM] book
3399     Boolean weird = FALSE;
3400
3401     fromX = fromY = toX = toY = -1;
3402     
3403     newGame = FALSE;
3404
3405     if (appData.debugMode)
3406       fprintf(debugFP, _("Parsing board: %s\n"), string);
3407
3408     move_str[0] = NULLCHAR;
3409     elapsed_time[0] = NULLCHAR;
3410     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3411         int  i = 0, j;
3412         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3413             if(string[i] == ' ') { ranks++; files = 0; }
3414             else files++;
3415             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3416             i++;
3417         }
3418         for(j = 0; j <i; j++) board_chars[j] = string[j];
3419         board_chars[i] = '\0';
3420         string += i + 1;
3421     }
3422     n = sscanf(string, PATTERN, &to_play, &double_push,
3423                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3424                &gamenum, white, black, &relation, &basetime, &increment,
3425                &white_stren, &black_stren, &white_time, &black_time,
3426                &moveNum, str, elapsed_time, move_str, &ics_flip,
3427                &ticking);
3428
3429    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3430                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3431      /* [HGM] We seem to switch variant during a game!
3432       * Try to guess new variant from board size
3433       */
3434           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3435           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3436           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3437           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3438           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3439           if(!weird) newVariant = VariantNormal;
3440           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3441           /* Get a move list just to see the header, which
3442              will tell us whether this is really bug or zh */
3443           if (ics_getting_history == H_FALSE) {
3444             ics_getting_history = H_REQUESTED;
3445             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3446             SendToICS(str);
3447           }
3448     }
3449
3450     if (n < 21) {
3451         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3452         DisplayError(str, 0);
3453         return;
3454     }
3455
3456     /* Convert the move number to internal form */
3457     moveNum = (moveNum - 1) * 2;
3458     if (to_play == 'B') moveNum++;
3459     if (moveNum >= MAX_MOVES) {
3460       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3461                         0, 1);
3462       return;
3463     }
3464     
3465     switch (relation) {
3466       case RELATION_OBSERVING_PLAYED:
3467       case RELATION_OBSERVING_STATIC:
3468         if (gamenum == -1) {
3469             /* Old ICC buglet */
3470             relation = RELATION_OBSERVING_STATIC;
3471         }
3472         newGameMode = IcsObserving;
3473         break;
3474       case RELATION_PLAYING_MYMOVE:
3475       case RELATION_PLAYING_NOTMYMOVE:
3476         newGameMode =
3477           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3478             IcsPlayingWhite : IcsPlayingBlack;
3479         break;
3480       case RELATION_EXAMINING:
3481         newGameMode = IcsExamining;
3482         break;
3483       case RELATION_ISOLATED_BOARD:
3484       default:
3485         /* Just display this board.  If user was doing something else,
3486            we will forget about it until the next board comes. */ 
3487         newGameMode = IcsIdle;
3488         break;
3489       case RELATION_STARTING_POSITION:
3490         newGameMode = gameMode;
3491         break;
3492     }
3493     
3494     /* Modify behavior for initial board display on move listing
3495        of wild games.
3496        */
3497     switch (ics_getting_history) {
3498       case H_FALSE:
3499       case H_REQUESTED:
3500         break;
3501       case H_GOT_REQ_HEADER:
3502       case H_GOT_UNREQ_HEADER:
3503         /* This is the initial position of the current game */
3504         gamenum = ics_gamenum;
3505         moveNum = 0;            /* old ICS bug workaround */
3506         if (to_play == 'B') {
3507           startedFromSetupPosition = TRUE;
3508           blackPlaysFirst = TRUE;
3509           moveNum = 1;
3510           if (forwardMostMove == 0) forwardMostMove = 1;
3511           if (backwardMostMove == 0) backwardMostMove = 1;
3512           if (currentMove == 0) currentMove = 1;
3513         }
3514         newGameMode = gameMode;
3515         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3516         break;
3517       case H_GOT_UNWANTED_HEADER:
3518         /* This is an initial board that we don't want */
3519         return;
3520       case H_GETTING_MOVES:
3521         /* Should not happen */
3522         DisplayError(_("Error gathering move list: extra board"), 0);
3523         ics_getting_history = H_FALSE;
3524         return;
3525     }
3526     
3527     /* Take action if this is the first board of a new game, or of a
3528        different game than is currently being displayed.  */
3529     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3530         relation == RELATION_ISOLATED_BOARD) {
3531         
3532         /* Forget the old game and get the history (if any) of the new one */
3533         if (gameMode != BeginningOfGame) {
3534           Reset(TRUE, TRUE);
3535         }
3536         newGame = TRUE;
3537         if (appData.autoRaiseBoard) BoardToTop();
3538         prevMove = -3;
3539         if (gamenum == -1) {
3540             newGameMode = IcsIdle;
3541         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3542                    appData.getMoveList) {
3543             /* Need to get game history */
3544             ics_getting_history = H_REQUESTED;
3545             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3546             SendToICS(str);
3547         }
3548         
3549         /* Initially flip the board to have black on the bottom if playing
3550            black or if the ICS flip flag is set, but let the user change
3551            it with the Flip View button. */
3552         flipView = appData.autoFlipView ? 
3553           (newGameMode == IcsPlayingBlack) || ics_flip :
3554           appData.flipView;
3555         
3556         /* Done with values from previous mode; copy in new ones */
3557         gameMode = newGameMode;
3558         ModeHighlight();
3559         ics_gamenum = gamenum;
3560         if (gamenum == gs_gamenum) {
3561             int klen = strlen(gs_kind);
3562             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3563             sprintf(str, "ICS %s", gs_kind);
3564             gameInfo.event = StrSave(str);
3565         } else {
3566             gameInfo.event = StrSave("ICS game");
3567         }
3568         gameInfo.site = StrSave(appData.icsHost);
3569         gameInfo.date = PGNDate();
3570         gameInfo.round = StrSave("-");
3571         gameInfo.white = StrSave(white);
3572         gameInfo.black = StrSave(black);
3573         timeControl = basetime * 60 * 1000;
3574         timeControl_2 = 0;
3575         timeIncrement = increment * 1000;
3576         movesPerSession = 0;
3577         gameInfo.timeControl = TimeControlTagValue();
3578         VariantSwitch(board, StringToVariant(gameInfo.event) );
3579   if (appData.debugMode) {
3580     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3581     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3582     setbuf(debugFP, NULL);
3583   }
3584
3585         gameInfo.outOfBook = NULL;
3586         
3587         /* Do we have the ratings? */
3588         if (strcmp(player1Name, white) == 0 &&
3589             strcmp(player2Name, black) == 0) {
3590             if (appData.debugMode)
3591               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3592                       player1Rating, player2Rating);
3593             gameInfo.whiteRating = player1Rating;
3594             gameInfo.blackRating = player2Rating;
3595         } else if (strcmp(player2Name, white) == 0 &&
3596                    strcmp(player1Name, black) == 0) {
3597             if (appData.debugMode)
3598               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3599                       player2Rating, player1Rating);
3600             gameInfo.whiteRating = player2Rating;
3601             gameInfo.blackRating = player1Rating;
3602         }
3603         player1Name[0] = player2Name[0] = NULLCHAR;
3604
3605         /* Silence shouts if requested */
3606         if (appData.quietPlay &&
3607             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3608             SendToICS(ics_prefix);
3609             SendToICS("set shout 0\n");
3610         }
3611     }
3612     
3613     /* Deal with midgame name changes */
3614     if (!newGame) {
3615         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3616             if (gameInfo.white) free(gameInfo.white);
3617             gameInfo.white = StrSave(white);
3618         }
3619         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3620             if (gameInfo.black) free(gameInfo.black);
3621             gameInfo.black = StrSave(black);
3622         }
3623     }
3624     
3625     /* Throw away game result if anything actually changes in examine mode */
3626     if (gameMode == IcsExamining && !newGame) {
3627         gameInfo.result = GameUnfinished;
3628         if (gameInfo.resultDetails != NULL) {
3629             free(gameInfo.resultDetails);
3630             gameInfo.resultDetails = NULL;
3631         }
3632     }
3633     
3634     /* In pausing && IcsExamining mode, we ignore boards coming
3635        in if they are in a different variation than we are. */
3636     if (pauseExamInvalid) return;
3637     if (pausing && gameMode == IcsExamining) {
3638         if (moveNum <= pauseExamForwardMostMove) {
3639             pauseExamInvalid = TRUE;
3640             forwardMostMove = pauseExamForwardMostMove;
3641             return;
3642         }
3643     }
3644     
3645   if (appData.debugMode) {
3646     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3647   }
3648     /* Parse the board */
3649     for (k = 0; k < ranks; k++) {
3650       for (j = 0; j < files; j++)
3651         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3652       if(gameInfo.holdingsWidth > 1) {
3653            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3654            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3655       }
3656     }
3657     CopyBoard(boards[moveNum], board);
3658     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3659     if (moveNum == 0) {
3660         startedFromSetupPosition =
3661           !CompareBoards(board, initialPosition);
3662         if(startedFromSetupPosition)
3663             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3664     }
3665
3666     /* [HGM] Set castling rights. Take the outermost Rooks,
3667        to make it also work for FRC opening positions. Note that board12
3668        is really defective for later FRC positions, as it has no way to
3669        indicate which Rook can castle if they are on the same side of King.
3670        For the initial position we grant rights to the outermost Rooks,
3671        and remember thos rights, and we then copy them on positions
3672        later in an FRC game. This means WB might not recognize castlings with
3673        Rooks that have moved back to their original position as illegal,
3674        but in ICS mode that is not its job anyway.
3675     */
3676     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3677     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3678
3679         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3680             if(board[0][i] == WhiteRook) j = i;
3681         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3682         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3683             if(board[0][i] == WhiteRook) j = i;
3684         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3686             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3687         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691
3692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696             if(board[BOARD_HEIGHT-1][k] == bKing)
3697                 initialRights[5] = castlingRights[moveNum][5] = k;
3698     } else { int r;
3699         r = castlingRights[moveNum][0] = initialRights[0];
3700         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3701         r = castlingRights[moveNum][1] = initialRights[1];
3702         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3703         r = castlingRights[moveNum][3] = initialRights[3];
3704         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3705         r = castlingRights[moveNum][4] = initialRights[4];
3706         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3707         /* wildcastle kludge: always assume King has rights */
3708         r = castlingRights[moveNum][2] = initialRights[2];
3709         r = castlingRights[moveNum][5] = initialRights[5];
3710     }
3711     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3712     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3713
3714     
3715     if (ics_getting_history == H_GOT_REQ_HEADER ||
3716         ics_getting_history == H_GOT_UNREQ_HEADER) {
3717         /* This was an initial position from a move list, not
3718            the current position */
3719         return;
3720     }
3721     
3722     /* Update currentMove and known move number limits */
3723     newMove = newGame || moveNum > forwardMostMove;
3724
3725     if (newGame) {
3726         forwardMostMove = backwardMostMove = currentMove = moveNum;
3727         if (gameMode == IcsExamining && moveNum == 0) {
3728           /* Workaround for ICS limitation: we are not told the wild
3729              type when starting to examine a game.  But if we ask for
3730              the move list, the move list header will tell us */
3731             ics_getting_history = H_REQUESTED;
3732             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3733             SendToICS(str);
3734         }
3735     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3736                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3737 #if ZIPPY
3738         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3739         /* [HGM] applied this also to an engine that is silently watching        */
3740         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3741             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3742             gameInfo.variant == currentlyInitializedVariant) {
3743           takeback = forwardMostMove - moveNum;
3744           for (i = 0; i < takeback; i++) {
3745             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3746             SendToProgram("undo\n", &first);
3747           }
3748         }
3749 #endif
3750
3751         forwardMostMove = moveNum;
3752         if (!pausing || currentMove > forwardMostMove)
3753           currentMove = forwardMostMove;
3754     } else {
3755         /* New part of history that is not contiguous with old part */ 
3756         if (pausing && gameMode == IcsExamining) {
3757             pauseExamInvalid = TRUE;
3758             forwardMostMove = pauseExamForwardMostMove;
3759             return;
3760         }
3761         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3762 #if ZIPPY
3763             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3764                 // [HGM] when we will receive the move list we now request, it will be
3765                 // fed to the engine from the first move on. So if the engine is not
3766                 // in the initial position now, bring it there.
3767                 InitChessProgram(&first, 0);
3768             }
3769 #endif
3770             ics_getting_history = H_REQUESTED;
3771             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3772             SendToICS(str);
3773         }
3774         forwardMostMove = backwardMostMove = currentMove = moveNum;
3775     }
3776     
3777     /* Update the clocks */
3778     if (strchr(elapsed_time, '.')) {
3779       /* Time is in ms */
3780       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3781       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3782     } else {
3783       /* Time is in seconds */
3784       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3785       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3786     }
3787       
3788
3789 #if ZIPPY
3790     if (appData.zippyPlay && newGame &&
3791         gameMode != IcsObserving && gameMode != IcsIdle &&
3792         gameMode != IcsExamining)
3793       ZippyFirstBoard(moveNum, basetime, increment);
3794 #endif
3795     
3796     /* Put the move on the move list, first converting
3797        to canonical algebraic form. */
3798     if (moveNum > 0) {
3799   if (appData.debugMode) {
3800     if (appData.debugMode) { int f = forwardMostMove;
3801         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3802                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3803     }
3804     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3805     fprintf(debugFP, "moveNum = %d\n", moveNum);
3806     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3807     setbuf(debugFP, NULL);
3808   }
3809         if (moveNum <= backwardMostMove) {
3810             /* We don't know what the board looked like before
3811                this move.  Punt. */
3812             strcpy(parseList[moveNum - 1], move_str);
3813             strcat(parseList[moveNum - 1], " ");
3814             strcat(parseList[moveNum - 1], elapsed_time);
3815             moveList[moveNum - 1][0] = NULLCHAR;
3816         } else if (strcmp(move_str, "none") == 0) {
3817             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3818             /* Again, we don't know what the board looked like;
3819                this is really the start of the game. */
3820             parseList[moveNum - 1][0] = NULLCHAR;
3821             moveList[moveNum - 1][0] = NULLCHAR;
3822             backwardMostMove = moveNum;
3823             startedFromSetupPosition = TRUE;
3824             fromX = fromY = toX = toY = -1;
3825         } else {
3826           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3827           //                 So we parse the long-algebraic move string in stead of the SAN move
3828           int valid; char buf[MSG_SIZ], *prom;
3829
3830           // str looks something like "Q/a1-a2"; kill the slash
3831           if(str[1] == '/') 
3832                 sprintf(buf, "%c%s", str[0], str+2);
3833           else  strcpy(buf, str); // might be castling
3834           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3835                 strcat(buf, prom); // long move lacks promo specification!
3836           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3837                 if(appData.debugMode) 
3838                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3839                 strcpy(move_str, buf);
3840           }
3841           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3842                                 &fromX, &fromY, &toX, &toY, &promoChar)
3843                || ParseOneMove(buf, moveNum - 1, &moveType,
3844                                 &fromX, &fromY, &toX, &toY, &promoChar);
3845           // end of long SAN patch
3846           if (valid) {
3847             (void) CoordsToAlgebraic(boards[moveNum - 1],
3848                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3849                                      fromY, fromX, toY, toX, promoChar,
3850                                      parseList[moveNum-1]);
3851             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3852                              castlingRights[moveNum]) ) {
3853               case MT_NONE:
3854               case MT_STALEMATE:
3855               default:
3856                 break;
3857               case MT_CHECK:
3858                 if(gameInfo.variant != VariantShogi)
3859                     strcat(parseList[moveNum - 1], "+");
3860                 break;
3861               case MT_CHECKMATE:
3862               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3863                 strcat(parseList[moveNum - 1], "#");
3864                 break;
3865             }
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             /* currentMoveString is set as a side-effect of ParseOneMove */
3869             strcpy(moveList[moveNum - 1], currentMoveString);
3870             strcat(moveList[moveNum - 1], "\n");
3871           } else {
3872             /* Move from ICS was illegal!?  Punt. */
3873   if (appData.debugMode) {
3874     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3875     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3876   }
3877             strcpy(parseList[moveNum - 1], move_str);
3878             strcat(parseList[moveNum - 1], " ");
3879             strcat(parseList[moveNum - 1], elapsed_time);
3880             moveList[moveNum - 1][0] = NULLCHAR;
3881             fromX = fromY = toX = toY = -1;
3882           }
3883         }
3884   if (appData.debugMode) {
3885     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3886     setbuf(debugFP, NULL);
3887   }
3888
3889 #if ZIPPY
3890         /* Send move to chess program (BEFORE animating it). */
3891         if (appData.zippyPlay && !newGame && newMove && 
3892            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3893
3894             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3895                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3896                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3897                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3898                             move_str);
3899                     DisplayError(str, 0);
3900                 } else {
3901                     if (first.sendTime) {
3902                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3903                     }
3904                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3905                     if (firstMove && !bookHit) {
3906                         firstMove = FALSE;
3907                         if (first.useColors) {
3908                           SendToProgram(gameMode == IcsPlayingWhite ?
3909                                         "white\ngo\n" :
3910                                         "black\ngo\n", &first);
3911                         } else {
3912                           SendToProgram("go\n", &first);
3913                         }
3914                         first.maybeThinking = TRUE;
3915                     }
3916                 }
3917             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3918               if (moveList[moveNum - 1][0] == NULLCHAR) {
3919                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3920                 DisplayError(str, 0);
3921               } else {
3922                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3923                 SendMoveToProgram(moveNum - 1, &first);
3924               }
3925             }
3926         }
3927 #endif
3928     }
3929
3930     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3931         /* If move comes from a remote source, animate it.  If it
3932            isn't remote, it will have already been animated. */
3933         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3934             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3935         }
3936         if (!pausing && appData.highlightLastMove) {
3937             SetHighlights(fromX, fromY, toX, toY);
3938         }
3939     }
3940     
3941     /* Start the clocks */
3942     whiteFlag = blackFlag = FALSE;
3943     appData.clockMode = !(basetime == 0 && increment == 0);
3944     if (ticking == 0) {
3945       ics_clock_paused = TRUE;
3946       StopClocks();
3947     } else if (ticking == 1) {
3948       ics_clock_paused = FALSE;
3949     }
3950     if (gameMode == IcsIdle ||
3951         relation == RELATION_OBSERVING_STATIC ||
3952         relation == RELATION_EXAMINING ||
3953         ics_clock_paused)
3954       DisplayBothClocks();
3955     else
3956       StartClocks();
3957     
3958     /* Display opponents and material strengths */
3959     if (gameInfo.variant != VariantBughouse &&
3960         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3961         if (tinyLayout || smallLayout) {
3962             if(gameInfo.variant == VariantNormal)
3963                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3964                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3965                     basetime, increment);
3966             else
3967                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3968                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3969                     basetime, increment, (int) gameInfo.variant);
3970         } else {
3971             if(gameInfo.variant == VariantNormal)
3972                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3974                     basetime, increment);
3975             else
3976                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3977                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3978                     basetime, increment, VariantName(gameInfo.variant));
3979         }
3980         DisplayTitle(str);
3981   if (appData.debugMode) {
3982     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3983   }
3984     }
3985
3986    
3987     /* Display the board */
3988     if (!pausing && !appData.noGUI) {
3989       
3990       if (appData.premove)
3991           if (!gotPremove || 
3992              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3993              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3994               ClearPremoveHighlights();
3995
3996       DrawPosition(FALSE, boards[currentMove]);
3997       DisplayMove(moveNum - 1);
3998       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3999             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4000               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4001         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4002       }
4003     }
4004
4005     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4006 #if ZIPPY
4007     if(bookHit) { // [HGM] book: simulate book reply
4008         static char bookMove[MSG_SIZ]; // a bit generous?
4009
4010         programStats.nodes = programStats.depth = programStats.time = 
4011         programStats.score = programStats.got_only_move = 0;
4012         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4013
4014         strcpy(bookMove, "move ");
4015         strcat(bookMove, bookHit);
4016         HandleMachineMove(bookMove, &first);
4017     }
4018 #endif
4019 }
4020
4021 void
4022 GetMoveListEvent()
4023 {
4024     char buf[MSG_SIZ];
4025     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4026         ics_getting_history = H_REQUESTED;
4027         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4028         SendToICS(buf);
4029     }
4030 }
4031
4032 void
4033 AnalysisPeriodicEvent(force)
4034      int force;
4035 {
4036     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4037          && !force) || !appData.periodicUpdates)
4038       return;
4039
4040     /* Send . command to Crafty to collect stats */
4041     SendToProgram(".\n", &first);
4042
4043     /* Don't send another until we get a response (this makes
4044        us stop sending to old Crafty's which don't understand
4045        the "." command (sending illegal cmds resets node count & time,
4046        which looks bad)) */
4047     programStats.ok_to_send = 0;
4048 }
4049
4050 void ics_update_width(new_width)
4051         int new_width;
4052 {
4053         ics_printf("set width %d\n", new_width);
4054 }
4055
4056 void
4057 SendMoveToProgram(moveNum, cps)
4058      int moveNum;
4059      ChessProgramState *cps;
4060 {
4061     char buf[MSG_SIZ];
4062
4063     if (cps->useUsermove) {
4064       SendToProgram("usermove ", cps);
4065     }
4066     if (cps->useSAN) {
4067       char *space;
4068       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4069         int len = space - parseList[moveNum];
4070         memcpy(buf, parseList[moveNum], len);
4071         buf[len++] = '\n';
4072         buf[len] = NULLCHAR;
4073       } else {
4074         sprintf(buf, "%s\n", parseList[moveNum]);
4075       }
4076       SendToProgram(buf, cps);
4077     } else {
4078       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4079         AlphaRank(moveList[moveNum], 4);
4080         SendToProgram(moveList[moveNum], cps);
4081         AlphaRank(moveList[moveNum], 4); // and back
4082       } else
4083       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4084        * the engine. It would be nice to have a better way to identify castle 
4085        * moves here. */
4086       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4087                                                                          && cps->useOOCastle) {
4088         int fromX = moveList[moveNum][0] - AAA; 
4089         int fromY = moveList[moveNum][1] - ONE;
4090         int toX = moveList[moveNum][2] - AAA; 
4091         int toY = moveList[moveNum][3] - ONE;
4092         if((boards[moveNum][fromY][fromX] == WhiteKing 
4093             && boards[moveNum][toY][toX] == WhiteRook)
4094            || (boards[moveNum][fromY][fromX] == BlackKing 
4095                && boards[moveNum][toY][toX] == BlackRook)) {
4096           if(toX > fromX) SendToProgram("O-O\n", cps);
4097           else SendToProgram("O-O-O\n", cps);
4098         }
4099         else SendToProgram(moveList[moveNum], cps);
4100       }
4101       else SendToProgram(moveList[moveNum], cps);
4102       /* End of additions by Tord */
4103     }
4104
4105     /* [HGM] setting up the opening has brought engine in force mode! */
4106     /*       Send 'go' if we are in a mode where machine should play. */
4107     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4108         (gameMode == TwoMachinesPlay   ||
4109 #ifdef ZIPPY
4110          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4111 #endif
4112          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4113         SendToProgram("go\n", cps);
4114   if (appData.debugMode) {
4115     fprintf(debugFP, "(extra)\n");
4116   }
4117     }
4118     setboardSpoiledMachineBlack = 0;
4119 }
4120
4121 void
4122 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4123      ChessMove moveType;
4124      int fromX, fromY, toX, toY;
4125 {
4126     char user_move[MSG_SIZ];
4127
4128     switch (moveType) {
4129       default:
4130         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4131                 (int)moveType, fromX, fromY, toX, toY);
4132         DisplayError(user_move + strlen("say "), 0);
4133         break;
4134       case WhiteKingSideCastle:
4135       case BlackKingSideCastle:
4136       case WhiteQueenSideCastleWild:
4137       case BlackQueenSideCastleWild:
4138       /* PUSH Fabien */
4139       case WhiteHSideCastleFR:
4140       case BlackHSideCastleFR:
4141       /* POP Fabien */
4142         sprintf(user_move, "o-o\n");
4143         break;
4144       case WhiteQueenSideCastle:
4145       case BlackQueenSideCastle:
4146       case WhiteKingSideCastleWild:
4147       case BlackKingSideCastleWild:
4148       /* PUSH Fabien */
4149       case WhiteASideCastleFR:
4150       case BlackASideCastleFR:
4151       /* POP Fabien */
4152         sprintf(user_move, "o-o-o\n");
4153         break;
4154       case WhitePromotionQueen:
4155       case BlackPromotionQueen:
4156       case WhitePromotionRook:
4157       case BlackPromotionRook:
4158       case WhitePromotionBishop:
4159       case BlackPromotionBishop:
4160       case WhitePromotionKnight:
4161       case BlackPromotionKnight:
4162       case WhitePromotionKing:
4163       case BlackPromotionKing:
4164       case WhitePromotionChancellor:
4165       case BlackPromotionChancellor:
4166       case WhitePromotionArchbishop:
4167       case BlackPromotionArchbishop:
4168         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4169             sprintf(user_move, "%c%c%c%c=%c\n",
4170                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4171                 PieceToChar(WhiteFerz));
4172         else if(gameInfo.variant == VariantGreat)
4173             sprintf(user_move, "%c%c%c%c=%c\n",
4174                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4175                 PieceToChar(WhiteMan));
4176         else
4177             sprintf(user_move, "%c%c%c%c=%c\n",
4178                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4179                 PieceToChar(PromoPiece(moveType)));
4180         break;
4181       case WhiteDrop:
4182       case BlackDrop:
4183         sprintf(user_move, "%c@%c%c\n",
4184                 ToUpper(PieceToChar((ChessSquare) fromX)),
4185                 AAA + toX, ONE + toY);
4186         break;
4187       case NormalMove:
4188       case WhiteCapturesEnPassant:
4189       case BlackCapturesEnPassant:
4190       case IllegalMove:  /* could be a variant we don't quite understand */
4191         sprintf(user_move, "%c%c%c%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4193         break;
4194     }
4195     SendToICS(user_move);
4196     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4197         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4198 }
4199
4200 void
4201 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4202      int rf, ff, rt, ft;
4203      char promoChar;
4204      char move[7];
4205 {
4206     if (rf == DROP_RANK) {
4207         sprintf(move, "%c@%c%c\n",
4208                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4209     } else {
4210         if (promoChar == 'x' || promoChar == NULLCHAR) {
4211             sprintf(move, "%c%c%c%c\n",
4212                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4213         } else {
4214             sprintf(move, "%c%c%c%c%c\n",
4215                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4216         }
4217     }
4218 }
4219
4220 void
4221 ProcessICSInitScript(f)
4222      FILE *f;
4223 {
4224     char buf[MSG_SIZ];
4225
4226     while (fgets(buf, MSG_SIZ, f)) {
4227         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4228     }
4229
4230     fclose(f);
4231 }
4232
4233
4234 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4235 void
4236 AlphaRank(char *move, int n)
4237 {
4238 //    char *p = move, c; int x, y;
4239
4240     if (appData.debugMode) {
4241         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4242     }
4243
4244     if(move[1]=='*' && 
4245        move[2]>='0' && move[2]<='9' &&
4246        move[3]>='a' && move[3]<='x'    ) {
4247         move[1] = '@';
4248         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4249         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4250     } else
4251     if(move[0]>='0' && move[0]<='9' &&
4252        move[1]>='a' && move[1]<='x' &&
4253        move[2]>='0' && move[2]<='9' &&
4254        move[3]>='a' && move[3]<='x'    ) {
4255         /* input move, Shogi -> normal */
4256         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4257         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4258         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4259         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4260     } else
4261     if(move[1]=='@' &&
4262        move[3]>='0' && move[3]<='9' &&
4263        move[2]>='a' && move[2]<='x'    ) {
4264         move[1] = '*';
4265         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4266         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4267     } else
4268     if(
4269        move[0]>='a' && move[0]<='x' &&
4270        move[3]>='0' && move[3]<='9' &&
4271        move[2]>='a' && move[2]<='x'    ) {
4272          /* output move, normal -> Shogi */
4273         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4274         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4275         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4276         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4277         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4278     }
4279     if (appData.debugMode) {
4280         fprintf(debugFP, "   out = '%s'\n", move);
4281     }
4282 }
4283
4284 /* Parser for moves from gnuchess, ICS, or user typein box */
4285 Boolean
4286 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4287      char *move;
4288      int moveNum;
4289      ChessMove *moveType;
4290      int *fromX, *fromY, *toX, *toY;
4291      char *promoChar;
4292 {       
4293     if (appData.debugMode) {
4294         fprintf(debugFP, "move to parse: %s\n", move);
4295     }
4296     *moveType = yylexstr(moveNum, move);
4297
4298     switch (*moveType) {
4299       case WhitePromotionChancellor:
4300       case BlackPromotionChancellor:
4301       case WhitePromotionArchbishop:
4302       case BlackPromotionArchbishop:
4303       case WhitePromotionQueen:
4304       case BlackPromotionQueen:
4305       case WhitePromotionRook:
4306       case BlackPromotionRook:
4307       case WhitePromotionBishop:
4308       case BlackPromotionBishop:
4309       case WhitePromotionKnight:
4310       case BlackPromotionKnight:
4311       case WhitePromotionKing:
4312       case BlackPromotionKing:
4313       case NormalMove:
4314       case WhiteCapturesEnPassant:
4315       case BlackCapturesEnPassant:
4316       case WhiteKingSideCastle:
4317       case WhiteQueenSideCastle:
4318       case BlackKingSideCastle:
4319       case BlackQueenSideCastle:
4320       case WhiteKingSideCastleWild:
4321       case WhiteQueenSideCastleWild:
4322       case BlackKingSideCastleWild:
4323       case BlackQueenSideCastleWild:
4324       /* Code added by Tord: */
4325       case WhiteHSideCastleFR:
4326       case WhiteASideCastleFR:
4327       case BlackHSideCastleFR:
4328       case BlackASideCastleFR:
4329       /* End of code added by Tord */
4330       case IllegalMove:         /* bug or odd chess variant */
4331         *fromX = currentMoveString[0] - AAA;
4332         *fromY = currentMoveString[1] - ONE;
4333         *toX = currentMoveString[2] - AAA;
4334         *toY = currentMoveString[3] - ONE;
4335         *promoChar = currentMoveString[4];
4336         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4337             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4338     if (appData.debugMode) {
4339         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4340     }
4341             *fromX = *fromY = *toX = *toY = 0;
4342             return FALSE;
4343         }
4344         if (appData.testLegality) {
4345           return (*moveType != IllegalMove);
4346         } else {
4347           return !(fromX == fromY && toX == toY);
4348         }
4349
4350       case WhiteDrop:
4351       case BlackDrop:
4352         *fromX = *moveType == WhiteDrop ?
4353           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4354           (int) CharToPiece(ToLower(currentMoveString[0]));
4355         *fromY = DROP_RANK;
4356         *toX = currentMoveString[2] - AAA;
4357         *toY = currentMoveString[3] - ONE;
4358         *promoChar = NULLCHAR;
4359         return TRUE;
4360
4361       case AmbiguousMove:
4362       case ImpossibleMove:
4363       case (ChessMove) 0:       /* end of file */
4364       case ElapsedTime:
4365       case Comment:
4366       case PGNTag:
4367       case NAG:
4368       case WhiteWins:
4369       case BlackWins:
4370       case GameIsDrawn:
4371       default:
4372     if (appData.debugMode) {
4373         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4374     }
4375         /* bug? */
4376         *fromX = *fromY = *toX = *toY = 0;
4377         *promoChar = NULLCHAR;
4378         return FALSE;
4379     }
4380 }
4381
4382 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4383 // All positions will have equal probability, but the current method will not provide a unique
4384 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4385 #define DARK 1
4386 #define LITE 2
4387 #define ANY 3
4388
4389 int squaresLeft[4];
4390 int piecesLeft[(int)BlackPawn];
4391 int seed, nrOfShuffles;
4392
4393 void GetPositionNumber()
4394 {       // sets global variable seed
4395         int i;
4396
4397         seed = appData.defaultFrcPosition;
4398         if(seed < 0) { // randomize based on time for negative FRC position numbers
4399                 for(i=0; i<50; i++) seed += random();
4400                 seed = random() ^ random() >> 8 ^ random() << 8;
4401                 if(seed<0) seed = -seed;
4402         }
4403 }
4404
4405 int put(Board board, int pieceType, int rank, int n, int shade)
4406 // put the piece on the (n-1)-th empty squares of the given shade
4407 {
4408         int i;
4409
4410         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4411                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4412                         board[rank][i] = (ChessSquare) pieceType;
4413                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4414                         squaresLeft[ANY]--;
4415                         piecesLeft[pieceType]--; 
4416                         return i;
4417                 }
4418         }
4419         return -1;
4420 }
4421
4422
4423 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4424 // calculate where the next piece goes, (any empty square), and put it there
4425 {
4426         int i;
4427
4428         i = seed % squaresLeft[shade];
4429         nrOfShuffles *= squaresLeft[shade];
4430         seed /= squaresLeft[shade];
4431         put(board, pieceType, rank, i, shade);
4432 }
4433
4434 void AddTwoPieces(Board board, int pieceType, int rank)
4435 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4436 {
4437         int i, n=squaresLeft[ANY], j=n-1, k;
4438
4439         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4440         i = seed % k;  // pick one
4441         nrOfShuffles *= k;
4442         seed /= k;
4443         while(i >= j) i -= j--;
4444         j = n - 1 - j; i += j;
4445         put(board, pieceType, rank, j, ANY);
4446         put(board, pieceType, rank, i, ANY);
4447 }
4448
4449 void SetUpShuffle(Board board, int number)
4450 {
4451         int i, p, first=1;
4452
4453         GetPositionNumber(); nrOfShuffles = 1;
4454
4455         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4456         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4457         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4458
4459         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4460
4461         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4462             p = (int) board[0][i];
4463             if(p < (int) BlackPawn) piecesLeft[p] ++;
4464             board[0][i] = EmptySquare;
4465         }
4466
4467         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4468             // shuffles restricted to allow normal castling put KRR first
4469             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4470                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4471             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4472                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4473             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4474                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4475             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4476                 put(board, WhiteRook, 0, 0, ANY);
4477             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4478         }
4479
4480         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4481             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4482             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4483                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4484                 while(piecesLeft[p] >= 2) {
4485                     AddOnePiece(board, p, 0, LITE);
4486                     AddOnePiece(board, p, 0, DARK);
4487                 }
4488                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4489             }
4490
4491         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4492             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4493             // but we leave King and Rooks for last, to possibly obey FRC restriction
4494             if(p == (int)WhiteRook) continue;
4495             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4496             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4497         }
4498
4499         // now everything is placed, except perhaps King (Unicorn) and Rooks
4500
4501         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4502             // Last King gets castling rights
4503             while(piecesLeft[(int)WhiteUnicorn]) {
4504                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4505                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4506             }
4507
4508             while(piecesLeft[(int)WhiteKing]) {
4509                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4511             }
4512
4513
4514         } else {
4515             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4516             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4517         }
4518
4519         // Only Rooks can be left; simply place them all
4520         while(piecesLeft[(int)WhiteRook]) {
4521                 i = put(board, WhiteRook, 0, 0, ANY);
4522                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4523                         if(first) {
4524                                 first=0;
4525                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4526                         }
4527                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4528                 }
4529         }
4530         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4531             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4532         }
4533
4534         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4535 }
4536
4537 int SetCharTable( char *table, const char * map )
4538 /* [HGM] moved here from winboard.c because of its general usefulness */
4539 /*       Basically a safe strcpy that uses the last character as King */
4540 {
4541     int result = FALSE; int NrPieces;
4542
4543     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4544                     && NrPieces >= 12 && !(NrPieces&1)) {
4545         int i; /* [HGM] Accept even length from 12 to 34 */
4546
4547         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4548         for( i=0; i<NrPieces/2-1; i++ ) {
4549             table[i] = map[i];
4550             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4551         }
4552         table[(int) WhiteKing]  = map[NrPieces/2-1];
4553         table[(int) BlackKing]  = map[NrPieces-1];
4554
4555         result = TRUE;
4556     }
4557
4558     return result;
4559 }
4560
4561 void Prelude(Board board)
4562 {       // [HGM] superchess: random selection of exo-pieces
4563         int i, j, k; ChessSquare p; 
4564         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4565
4566         GetPositionNumber(); // use FRC position number
4567
4568         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4569             SetCharTable(pieceToChar, appData.pieceToCharTable);
4570             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4571                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4572         }
4573
4574         j = seed%4;                 seed /= 4; 
4575         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4576         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4577         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4578         j = seed%3 + (seed%3 >= j); seed /= 3; 
4579         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4580         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4581         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4582         j = seed%3;                 seed /= 3; 
4583         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4584         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4585         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4586         j = seed%2 + (seed%2 >= j); seed /= 2; 
4587         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4588         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4589         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4590         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4591         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4592         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4593         put(board, exoPieces[0],    0, 0, ANY);
4594         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4595 }
4596
4597 void
4598 InitPosition(redraw)
4599      int redraw;
4600 {
4601     ChessSquare (* pieces)[BOARD_SIZE];
4602     int i, j, pawnRow, overrule,
4603     oldx = gameInfo.boardWidth,
4604     oldy = gameInfo.boardHeight,
4605     oldh = gameInfo.holdingsWidth,
4606     oldv = gameInfo.variant;
4607
4608     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4609
4610     /* [AS] Initialize pv info list [HGM] and game status */
4611     {
4612         for( i=0; i<MAX_MOVES; i++ ) {
4613             pvInfoList[i].depth = 0;
4614             epStatus[i]=EP_NONE;
4615             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4616         }
4617
4618         initialRulePlies = 0; /* 50-move counter start */
4619
4620         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4621         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4622     }
4623
4624     
4625     /* [HGM] logic here is completely changed. In stead of full positions */
4626     /* the initialized data only consist of the two backranks. The switch */
4627     /* selects which one we will use, which is than copied to the Board   */
4628     /* initialPosition, which for the rest is initialized by Pawns and    */
4629     /* empty squares. This initial position is then copied to boards[0],  */
4630     /* possibly after shuffling, so that it remains available.            */
4631
4632     gameInfo.holdingsWidth = 0; /* default board sizes */
4633     gameInfo.boardWidth    = 8;
4634     gameInfo.boardHeight   = 8;
4635     gameInfo.holdingsSize  = 0;
4636     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4637     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4638     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4639
4640     switch (gameInfo.variant) {
4641     case VariantFischeRandom:
4642       shuffleOpenings = TRUE;
4643     default:
4644       pieces = FIDEArray;
4645       break;
4646     case VariantShatranj:
4647       pieces = ShatranjArray;
4648       nrCastlingRights = 0;
4649       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4650       break;
4651     case VariantTwoKings:
4652       pieces = twoKingsArray;
4653       break;
4654     case VariantCapaRandom:
4655       shuffleOpenings = TRUE;
4656     case VariantCapablanca:
4657       pieces = CapablancaArray;
4658       gameInfo.boardWidth = 10;
4659       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4660       break;
4661     case VariantGothic:
4662       pieces = GothicArray;
4663       gameInfo.boardWidth = 10;
4664       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4665       break;
4666     case VariantJanus:
4667       pieces = JanusArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4670       nrCastlingRights = 6;
4671         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4672         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4673         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4674         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4675         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4676         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4677       break;
4678     case VariantFalcon:
4679       pieces = FalconArray;
4680       gameInfo.boardWidth = 10;
4681       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4682       break;
4683     case VariantXiangqi:
4684       pieces = XiangqiArray;
4685       gameInfo.boardWidth  = 9;
4686       gameInfo.boardHeight = 10;
4687       nrCastlingRights = 0;
4688       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4689       break;
4690     case VariantShogi:
4691       pieces = ShogiArray;
4692       gameInfo.boardWidth  = 9;
4693       gameInfo.boardHeight = 9;
4694       gameInfo.holdingsSize = 7;
4695       nrCastlingRights = 0;
4696       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4697       break;
4698     case VariantCourier:
4699       pieces = CourierArray;
4700       gameInfo.boardWidth  = 12;
4701       nrCastlingRights = 0;
4702       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4703       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4704       break;
4705     case VariantKnightmate:
4706       pieces = KnightmateArray;
4707       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4708       break;
4709     case VariantFairy:
4710       pieces = fairyArray;
4711       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4712       break;
4713     case VariantGreat:
4714       pieces = GreatArray;
4715       gameInfo.boardWidth = 10;
4716       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4717       gameInfo.holdingsSize = 8;
4718       break;
4719     case VariantSuper:
4720       pieces = FIDEArray;
4721       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4722       gameInfo.holdingsSize = 8;
4723       startedFromSetupPosition = TRUE;
4724       break;
4725     case VariantCrazyhouse:
4726     case VariantBughouse:
4727       pieces = FIDEArray;
4728       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4729       gameInfo.holdingsSize = 5;
4730       break;
4731     case VariantWildCastle:
4732       pieces = FIDEArray;
4733       /* !!?shuffle with kings guaranteed to be on d or e file */
4734       shuffleOpenings = 1;
4735       break;
4736     case VariantNoCastle:
4737       pieces = FIDEArray;
4738       nrCastlingRights = 0;
4739       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4740       /* !!?unconstrained back-rank shuffle */
4741       shuffleOpenings = 1;
4742       break;
4743     }
4744
4745     overrule = 0;
4746     if(appData.NrFiles >= 0) {
4747         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4748         gameInfo.boardWidth = appData.NrFiles;
4749     }
4750     if(appData.NrRanks >= 0) {
4751         gameInfo.boardHeight = appData.NrRanks;
4752     }
4753     if(appData.holdingsSize >= 0) {
4754         i = appData.holdingsSize;
4755         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4756         gameInfo.holdingsSize = i;
4757     }
4758     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4759     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4760         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4761
4762     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4763     if(pawnRow < 1) pawnRow = 1;
4764
4765     /* User pieceToChar list overrules defaults */
4766     if(appData.pieceToCharTable != NULL)
4767         SetCharTable(pieceToChar, appData.pieceToCharTable);
4768
4769     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4770
4771         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4772             s = (ChessSquare) 0; /* account holding counts in guard band */
4773         for( i=0; i<BOARD_HEIGHT; i++ )
4774             initialPosition[i][j] = s;
4775
4776         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4777         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4778         initialPosition[pawnRow][j] = WhitePawn;
4779         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4780         if(gameInfo.variant == VariantXiangqi) {
4781             if(j&1) {
4782                 initialPosition[pawnRow][j] = 
4783                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4784                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4785                    initialPosition[2][j] = WhiteCannon;
4786                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4787                 }
4788             }
4789         }
4790         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4791     }
4792     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4793
4794             j=BOARD_LEFT+1;
4795             initialPosition[1][j] = WhiteBishop;
4796             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4797             j=BOARD_RGHT-2;
4798             initialPosition[1][j] = WhiteRook;
4799             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4800     }
4801
4802     if( nrCastlingRights == -1) {
4803         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4804         /*       This sets default castling rights from none to normal corners   */
4805         /* Variants with other castling rights must set them themselves above    */
4806         nrCastlingRights = 6;
4807        
4808         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4809         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4810         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4811         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4812         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4813         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4814      }
4815
4816      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4817      if(gameInfo.variant == VariantGreat) { // promotion commoners
4818         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4819         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4820         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4821         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4822      }
4823   if (appData.debugMode) {
4824     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4825   }
4826     if(shuffleOpenings) {
4827         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4828         startedFromSetupPosition = TRUE;
4829     }
4830     if(startedFromPositionFile) {
4831       /* [HGM] loadPos: use PositionFile for every new game */
4832       CopyBoard(initialPosition, filePosition);
4833       for(i=0; i<nrCastlingRights; i++)
4834           castlingRights[0][i] = initialRights[i] = fileRights[i];
4835       startedFromSetupPosition = TRUE;
4836     }
4837
4838     CopyBoard(boards[0], initialPosition);
4839
4840     if(oldx != gameInfo.boardWidth ||
4841        oldy != gameInfo.boardHeight ||
4842        oldh != gameInfo.holdingsWidth
4843 #ifdef GOTHIC
4844        || oldv == VariantGothic ||        // For licensing popups
4845        gameInfo.variant == VariantGothic
4846 #endif
4847 #ifdef FALCON
4848        || oldv == VariantFalcon ||
4849        gameInfo.variant == VariantFalcon
4850 #endif
4851                                          )
4852             InitDrawingSizes(-2 ,0);
4853
4854     if (redraw)
4855       DrawPosition(TRUE, boards[currentMove]);
4856 }
4857
4858 void
4859 SendBoard(cps, moveNum)
4860      ChessProgramState *cps;
4861      int moveNum;
4862 {
4863     char message[MSG_SIZ];
4864     
4865     if (cps->useSetboard) {
4866       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4867       sprintf(message, "setboard %s\n", fen);
4868       SendToProgram(message, cps);
4869       free(fen);
4870
4871     } else {
4872       ChessSquare *bp;
4873       int i, j;
4874       /* Kludge to set black to move, avoiding the troublesome and now
4875        * deprecated "black" command.
4876        */
4877       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4878
4879       SendToProgram("edit\n", cps);
4880       SendToProgram("#\n", cps);
4881       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4882         bp = &boards[moveNum][i][BOARD_LEFT];
4883         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4884           if ((int) *bp < (int) BlackPawn) {
4885             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4886                     AAA + j, ONE + i);
4887             if(message[0] == '+' || message[0] == '~') {
4888                 sprintf(message, "%c%c%c+\n",
4889                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4890                         AAA + j, ONE + i);
4891             }
4892             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4893                 message[1] = BOARD_RGHT   - 1 - j + '1';
4894                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4895             }
4896             SendToProgram(message, cps);
4897           }
4898         }
4899       }
4900     
4901       SendToProgram("c\n", cps);
4902       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4903         bp = &boards[moveNum][i][BOARD_LEFT];
4904         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4905           if (((int) *bp != (int) EmptySquare)
4906               && ((int) *bp >= (int) BlackPawn)) {
4907             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4908                     AAA + j, ONE + i);
4909             if(message[0] == '+' || message[0] == '~') {
4910                 sprintf(message, "%c%c%c+\n",
4911                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4912                         AAA + j, ONE + i);
4913             }
4914             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4915                 message[1] = BOARD_RGHT   - 1 - j + '1';
4916                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4917             }
4918             SendToProgram(message, cps);
4919           }
4920         }
4921       }
4922     
4923       SendToProgram(".\n", cps);
4924     }
4925     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4926 }
4927
4928 int
4929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4930 {
4931     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4932     /* [HGM] add Shogi promotions */
4933     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4934     ChessSquare piece;
4935     ChessMove moveType;
4936     Boolean premove;
4937
4938     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4939     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4940
4941     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4942       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4943         return FALSE;
4944
4945     piece = boards[currentMove][fromY][fromX];
4946     if(gameInfo.variant == VariantShogi) {
4947         promotionZoneSize = 3;
4948         highestPromotingPiece = (int)WhiteFerz;
4949     }
4950
4951     // next weed out all moves that do not touch the promotion zone at all
4952     if((int)piece >= BlackPawn) {
4953         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4954              return FALSE;
4955         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4956     } else {
4957         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4958            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4959     }
4960
4961     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4962
4963     // weed out mandatory Shogi promotions
4964     if(gameInfo.variant == VariantShogi) {
4965         if(piece >= BlackPawn) {
4966             if(toY == 0 && piece == BlackPawn ||
4967                toY == 0 && piece == BlackQueen ||
4968                toY <= 1 && piece == BlackKnight) {
4969                 *promoChoice = '+';
4970                 return FALSE;
4971             }
4972         } else {
4973             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4974                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4975                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4976                 *promoChoice = '+';
4977                 return FALSE;
4978             }
4979         }
4980     }
4981
4982     // weed out obviously illegal Pawn moves
4983     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4984         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4985         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4986         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4987         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4988         // note we are not allowed to test for valid (non-)capture, due to premove
4989     }
4990
4991     // we either have a choice what to promote to, or (in Shogi) whether to promote
4992     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4993         *promoChoice = PieceToChar(BlackFerz);  // no choice
4994         return FALSE;
4995     }
4996     if(appData.alwaysPromoteToQueen) { // predetermined
4997         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4998              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4999         else *promoChoice = PieceToChar(BlackQueen);
5000         return FALSE;
5001     }
5002
5003     // suppress promotion popup on illegal moves that are not premoves
5004     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5005               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5006     if(appData.testLegality && !premove) {
5007         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5008                         epStatus[currentMove], castlingRights[currentMove],
5009                         fromY, fromX, toY, toX, NULLCHAR);
5010         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5011            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5012             return FALSE;
5013     }
5014
5015     return TRUE;
5016 }
5017
5018 int
5019 InPalace(row, column)
5020      int row, column;
5021 {   /* [HGM] for Xiangqi */
5022     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5023          column < (BOARD_WIDTH + 4)/2 &&
5024          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5025     return FALSE;
5026 }
5027
5028 int
5029 PieceForSquare (x, y)
5030      int x;
5031      int y;
5032 {
5033   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5034      return -1;
5035   else
5036      return boards[currentMove][y][x];
5037 }
5038
5039 int
5040 OKToStartUserMove(x, y)
5041      int x, y;
5042 {
5043     ChessSquare from_piece;
5044     int white_piece;
5045
5046     if (matchMode) return FALSE;
5047     if (gameMode == EditPosition) return TRUE;
5048
5049     if (x >= 0 && y >= 0)
5050       from_piece = boards[currentMove][y][x];
5051     else
5052       from_piece = EmptySquare;
5053
5054     if (from_piece == EmptySquare) return FALSE;
5055
5056     white_piece = (int)from_piece >= (int)WhitePawn &&
5057       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5058
5059     switch (gameMode) {
5060       case PlayFromGameFile:
5061       case AnalyzeFile:
5062       case TwoMachinesPlay:
5063       case EndOfGame:
5064         return FALSE;
5065
5066       case IcsObserving:
5067       case IcsIdle:
5068         return FALSE;
5069
5070       case MachinePlaysWhite:
5071       case IcsPlayingBlack:
5072         if (appData.zippyPlay) return FALSE;
5073         if (white_piece) {
5074             DisplayMoveError(_("You are playing Black"));
5075             return FALSE;
5076         }
5077         break;
5078
5079       case MachinePlaysBlack:
5080       case IcsPlayingWhite:
5081         if (appData.zippyPlay) return FALSE;
5082         if (!white_piece) {
5083             DisplayMoveError(_("You are playing White"));
5084             return FALSE;
5085         }
5086         break;
5087
5088       case EditGame:
5089         if (!white_piece && WhiteOnMove(currentMove)) {
5090             DisplayMoveError(_("It is White's turn"));
5091             return FALSE;
5092         }           
5093         if (white_piece && !WhiteOnMove(currentMove)) {
5094             DisplayMoveError(_("It is Black's turn"));
5095             return FALSE;
5096         }           
5097         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5098             /* Editing correspondence game history */
5099             /* Could disallow this or prompt for confirmation */
5100             cmailOldMove = -1;
5101         }
5102         if (currentMove < forwardMostMove) {
5103             /* Discarding moves */
5104             /* Could prompt for confirmation here,
5105                but I don't think that's such a good idea */
5106             forwardMostMove = currentMove;
5107         }
5108         break;
5109
5110       case BeginningOfGame:
5111         if (appData.icsActive) return FALSE;
5112         if (!appData.noChessProgram) {
5113             if (!white_piece) {
5114                 DisplayMoveError(_("You are playing White"));
5115                 return FALSE;
5116             }
5117         }
5118         break;
5119         
5120       case Training:
5121         if (!white_piece && WhiteOnMove(currentMove)) {
5122             DisplayMoveError(_("It is White's turn"));
5123             return FALSE;
5124         }           
5125         if (white_piece && !WhiteOnMove(currentMove)) {
5126             DisplayMoveError(_("It is Black's turn"));
5127             return FALSE;
5128         }           
5129         break;
5130
5131       default:
5132       case IcsExamining:
5133         break;
5134     }
5135     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5136         && gameMode != AnalyzeFile && gameMode != Training) {
5137         DisplayMoveError(_("Displayed position is not current"));
5138         return FALSE;
5139     }
5140     return TRUE;
5141 }
5142
5143 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5144 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5145 int lastLoadGameUseList = FALSE;
5146 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5147 ChessMove lastLoadGameStart = (ChessMove) 0;
5148
5149 ChessMove
5150 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5151      int fromX, fromY, toX, toY;
5152      int promoChar;
5153      Boolean captureOwn;
5154 {
5155     ChessMove moveType;
5156     ChessSquare pdown, pup;
5157
5158     /* Check if the user is playing in turn.  This is complicated because we
5159        let the user "pick up" a piece before it is his turn.  So the piece he
5160        tried to pick up may have been captured by the time he puts it down!
5161        Therefore we use the color the user is supposed to be playing in this
5162        test, not the color of the piece that is currently on the starting
5163        square---except in EditGame mode, where the user is playing both
5164        sides; fortunately there the capture race can't happen.  (It can
5165        now happen in IcsExamining mode, but that's just too bad.  The user
5166        will get a somewhat confusing message in that case.)
5167        */
5168
5169     switch (gameMode) {
5170       case PlayFromGameFile:
5171       case AnalyzeFile:
5172       case TwoMachinesPlay:
5173       case EndOfGame:
5174       case IcsObserving:
5175       case IcsIdle:
5176         /* We switched into a game mode where moves are not accepted,
5177            perhaps while the mouse button was down. */
5178         return ImpossibleMove;
5179
5180       case MachinePlaysWhite:
5181         /* User is moving for Black */
5182         if (WhiteOnMove(currentMove)) {
5183             DisplayMoveError(_("It is White's turn"));
5184             return ImpossibleMove;
5185         }
5186         break;
5187
5188       case MachinePlaysBlack:
5189         /* User is moving for White */
5190         if (!WhiteOnMove(currentMove)) {
5191             DisplayMoveError(_("It is Black's turn"));
5192             return ImpossibleMove;
5193         }
5194         break;
5195
5196       case EditGame:
5197       case IcsExamining:
5198       case BeginningOfGame:
5199       case AnalyzeMode:
5200       case Training:
5201         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5202             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5203             /* User is moving for Black */
5204             if (WhiteOnMove(currentMove)) {
5205                 DisplayMoveError(_("It is White's turn"));
5206                 return ImpossibleMove;
5207             }
5208         } else {
5209             /* User is moving for White */
5210             if (!WhiteOnMove(currentMove)) {
5211                 DisplayMoveError(_("It is Black's turn"));
5212                 return ImpossibleMove;
5213             }
5214         }
5215         break;
5216
5217       case IcsPlayingBlack:
5218         /* User is moving for Black */
5219         if (WhiteOnMove(currentMove)) {
5220             if (!appData.premove) {
5221                 DisplayMoveError(_("It is White's turn"));
5222             } else if (toX >= 0 && toY >= 0) {
5223                 premoveToX = toX;
5224                 premoveToY = toY;
5225                 premoveFromX = fromX;
5226                 premoveFromY = fromY;
5227                 premovePromoChar = promoChar;
5228                 gotPremove = 1;
5229                 if (appData.debugMode) 
5230                     fprintf(debugFP, "Got premove: fromX %d,"
5231                             "fromY %d, toX %d, toY %d\n",
5232                             fromX, fromY, toX, toY);
5233             }
5234             return ImpossibleMove;
5235         }
5236         break;
5237
5238       case IcsPlayingWhite:
5239         /* User is moving for White */
5240         if (!WhiteOnMove(currentMove)) {
5241             if (!appData.premove) {
5242                 DisplayMoveError(_("It is Black's turn"));
5243             } else if (toX >= 0 && toY >= 0) {
5244                 premoveToX = toX;
5245                 premoveToY = toY;
5246                 premoveFromX = fromX;
5247                 premoveFromY = fromY;
5248                 premovePromoChar = promoChar;
5249                 gotPremove = 1;
5250                 if (appData.debugMode) 
5251                     fprintf(debugFP, "Got premove: fromX %d,"
5252                             "fromY %d, toX %d, toY %d\n",
5253                             fromX, fromY, toX, toY);
5254             }
5255             return ImpossibleMove;
5256         }
5257         break;
5258
5259       default:
5260         break;
5261
5262       case EditPosition:
5263         /* EditPosition, empty square, or different color piece;
5264            click-click move is possible */
5265         if (toX == -2 || toY == -2) {
5266             boards[0][fromY][fromX] = EmptySquare;
5267             return AmbiguousMove;
5268         } else if (toX >= 0 && toY >= 0) {
5269             boards[0][toY][toX] = boards[0][fromY][fromX];
5270             boards[0][fromY][fromX] = EmptySquare;
5271             return AmbiguousMove;
5272         }
5273         return ImpossibleMove;
5274     }
5275
5276     if(toX < 0 || toY < 0) return ImpossibleMove;
5277     pdown = boards[currentMove][fromY][fromX];
5278     pup = boards[currentMove][toY][toX];
5279
5280     /* [HGM] If move started in holdings, it means a drop */
5281     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5282          if( pup != EmptySquare ) return ImpossibleMove;
5283          if(appData.testLegality) {
5284              /* it would be more logical if LegalityTest() also figured out
5285               * which drops are legal. For now we forbid pawns on back rank.
5286               * Shogi is on its own here...
5287               */
5288              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5289                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5290                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5291          }
5292          return WhiteDrop; /* Not needed to specify white or black yet */
5293     }
5294
5295     userOfferedDraw = FALSE;
5296         
5297     /* [HGM] always test for legality, to get promotion info */
5298     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5299                           epStatus[currentMove], castlingRights[currentMove],
5300                                          fromY, fromX, toY, toX, promoChar);
5301     /* [HGM] but possibly ignore an IllegalMove result */
5302     if (appData.testLegality) {
5303         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304             DisplayMoveError(_("Illegal move"));
5305             return ImpossibleMove;
5306         }
5307     }
5308 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5309     return moveType;
5310     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311        function is made into one that returns an OK move type if FinishMove
5312        should be called. This to give the calling driver routine the
5313        opportunity to finish the userMove input with a promotion popup,
5314        without bothering the user with this for invalid or illegal moves */
5315
5316 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5317 }
5318
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5320 int
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5322      ChessMove moveType;
5323      int fromX, fromY, toX, toY;
5324      /*char*/int promoChar;
5325 {
5326     char *bookHit = 0;
5327 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5328     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5329         // [HGM] superchess: suppress promotions to non-available piece
5330         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5331         if(WhiteOnMove(currentMove)) {
5332             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5333         } else {
5334             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5335         }
5336     }
5337
5338     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5339        move type in caller when we know the move is a legal promotion */
5340     if(moveType == NormalMove && promoChar)
5341         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5342 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5343     /* [HGM] convert drag-and-drop piece drops to standard form */
5344     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5345          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5346            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5347                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5348 //         fromX = boards[currentMove][fromY][fromX];
5349            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5350            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5351            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5352            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5353          fromY = DROP_RANK;
5354     }
5355
5356     /* [HGM] <popupFix> The following if has been moved here from
5357        UserMoveEvent(). Because it seemed to belon here (why not allow
5358        piece drops in training games?), and because it can only be
5359        performed after it is known to what we promote. */
5360     if (gameMode == Training) {
5361       /* compare the move played on the board to the next move in the
5362        * game. If they match, display the move and the opponent's response. 
5363        * If they don't match, display an error message.
5364        */
5365       int saveAnimate;
5366       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5367       CopyBoard(testBoard, boards[currentMove]);
5368       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5369
5370       if (CompareBoards(testBoard, boards[currentMove+1])) {
5371         ForwardInner(currentMove+1);
5372
5373         /* Autoplay the opponent's response.
5374          * if appData.animate was TRUE when Training mode was entered,
5375          * the response will be animated.
5376          */
5377         saveAnimate = appData.animate;
5378         appData.animate = animateTraining;
5379         ForwardInner(currentMove+1);
5380         appData.animate = saveAnimate;
5381
5382         /* check for the end of the game */
5383         if (currentMove >= forwardMostMove) {
5384           gameMode = PlayFromGameFile;
5385           ModeHighlight();
5386           SetTrainingModeOff();
5387           DisplayInformation(_("End of game"));
5388         }
5389       } else {
5390         DisplayError(_("Incorrect move"), 0);
5391       }
5392       return 1;
5393     }
5394
5395   /* Ok, now we know that the move is good, so we can kill
5396      the previous line in Analysis Mode */
5397   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5398     forwardMostMove = currentMove;
5399   }
5400
5401   /* If we need the chess program but it's dead, restart it */
5402   ResurrectChessProgram();
5403
5404   /* A user move restarts a paused game*/
5405   if (pausing)
5406     PauseEvent();
5407
5408   thinkOutput[0] = NULLCHAR;
5409
5410   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5411
5412   if (gameMode == BeginningOfGame) {
5413     if (appData.noChessProgram) {
5414       gameMode = EditGame;
5415       SetGameInfo();
5416     } else {
5417       char buf[MSG_SIZ];
5418       gameMode = MachinePlaysBlack;
5419       StartClocks();
5420       SetGameInfo();
5421       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5422       DisplayTitle(buf);
5423       if (first.sendName) {
5424         sprintf(buf, "name %s\n", gameInfo.white);
5425         SendToProgram(buf, &first);
5426       }
5427       StartClocks();
5428     }
5429     ModeHighlight();
5430   }
5431 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5432   /* Relay move to ICS or chess engine */
5433   if (appData.icsActive) {
5434     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5435         gameMode == IcsExamining) {
5436       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5437       ics_user_moved = 1;
5438     }
5439   } else {
5440     if (first.sendTime && (gameMode == BeginningOfGame ||
5441                            gameMode == MachinePlaysWhite ||
5442                            gameMode == MachinePlaysBlack)) {
5443       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5444     }
5445     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5446          // [HGM] book: if program might be playing, let it use book
5447         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5448         first.maybeThinking = TRUE;
5449     } else SendMoveToProgram(forwardMostMove-1, &first);
5450     if (currentMove == cmailOldMove + 1) {
5451       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5452     }
5453   }
5454
5455   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5456
5457   switch (gameMode) {
5458   case EditGame:
5459     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5460                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5461     case MT_NONE:
5462     case MT_CHECK:
5463       break;
5464     case MT_CHECKMATE:
5465     case MT_STAINMATE:
5466       if (WhiteOnMove(currentMove)) {
5467         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5468       } else {
5469         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5470       }
5471       break;
5472     case MT_STALEMATE:
5473       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5474       break;
5475     }
5476     break;
5477     
5478   case MachinePlaysBlack:
5479   case MachinePlaysWhite:
5480     /* disable certain menu options while machine is thinking */
5481     SetMachineThinkingEnables();
5482     break;
5483
5484   default:
5485     break;
5486   }
5487
5488   if(bookHit) { // [HGM] book: simulate book reply
5489         static char bookMove[MSG_SIZ]; // a bit generous?
5490
5491         programStats.nodes = programStats.depth = programStats.time = 
5492         programStats.score = programStats.got_only_move = 0;
5493         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5494
5495         strcpy(bookMove, "move ");
5496         strcat(bookMove, bookHit);
5497         HandleMachineMove(bookMove, &first);
5498   }
5499   return 1;
5500 }
5501
5502 void
5503 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5504      int fromX, fromY, toX, toY;
5505      int promoChar;
5506 {
5507     /* [HGM] This routine was added to allow calling of its two logical
5508        parts from other modules in the old way. Before, UserMoveEvent()
5509        automatically called FinishMove() if the move was OK, and returned
5510        otherwise. I separated the two, in order to make it possible to
5511        slip a promotion popup in between. But that it always needs two
5512        calls, to the first part, (now called UserMoveTest() ), and to
5513        FinishMove if the first part succeeded. Calls that do not need
5514        to do anything in between, can call this routine the old way. 
5515     */
5516     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5517 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5518     if(moveType == AmbiguousMove)
5519         DrawPosition(FALSE, boards[currentMove]);
5520     else if(moveType != ImpossibleMove && moveType != Comment)
5521         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5522 }
5523
5524 void LeftClick(ClickType clickType, int xPix, int yPix)
5525 {
5526     int x, y;
5527     Boolean saveAnimate;
5528     static int second = 0, promotionChoice = 0;
5529     char promoChoice = NULLCHAR;
5530
5531     if (clickType == Press) ErrorPopDown();
5532
5533     x = EventToSquare(xPix, BOARD_WIDTH);
5534     y = EventToSquare(yPix, BOARD_HEIGHT);
5535     if (!flipView && y >= 0) {
5536         y = BOARD_HEIGHT - 1 - y;
5537     }
5538     if (flipView && x >= 0) {
5539         x = BOARD_WIDTH - 1 - x;
5540     }
5541
5542     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5543         if(clickType == Release) return; // ignore upclick of click-click destination
5544         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5545         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5546         if(gameInfo.holdingsWidth && 
5547                 (WhiteOnMove(currentMove) 
5548                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5549                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5550             // click in right holdings, for determining promotion piece
5551             ChessSquare p = boards[currentMove][y][x];
5552             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5553             if(p != EmptySquare) {
5554                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5555                 fromX = fromY = -1;
5556                 return;
5557             }
5558         }
5559         DrawPosition(FALSE, boards[currentMove]);
5560         return;
5561     }
5562
5563     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5564     if(clickType == Press
5565             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5566               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5567               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5568         return;
5569
5570     if (fromX == -1) {
5571         if (clickType == Press) {
5572             /* First square */
5573             if (OKToStartUserMove(x, y)) {
5574                 fromX = x;
5575                 fromY = y;
5576                 second = 0;
5577                 DragPieceBegin(xPix, yPix);
5578                 if (appData.highlightDragging) {
5579                     SetHighlights(x, y, -1, -1);
5580                 }
5581             }
5582         }
5583         return;
5584     }
5585
5586     /* fromX != -1 */
5587     if (clickType == Press && gameMode != EditPosition) {
5588         ChessSquare fromP;
5589         ChessSquare toP;
5590         int frc;
5591
5592         // ignore off-board to clicks
5593         if(y < 0 || x < 0) return;
5594
5595         /* Check if clicking again on the same color piece */
5596         fromP = boards[currentMove][fromY][fromX];
5597         toP = boards[currentMove][y][x];
5598         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5599         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5600              WhitePawn <= toP && toP <= WhiteKing &&
5601              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5602              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5603             (BlackPawn <= fromP && fromP <= BlackKing && 
5604              BlackPawn <= toP && toP <= BlackKing &&
5605              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5606              !(fromP == BlackKing && toP == BlackRook && frc))) {
5607             /* Clicked again on same color piece -- changed his mind */
5608             second = (x == fromX && y == fromY);
5609             if (appData.highlightDragging) {
5610                 SetHighlights(x, y, -1, -1);
5611             } else {
5612                 ClearHighlights();
5613             }
5614             if (OKToStartUserMove(x, y)) {
5615                 fromX = x;
5616                 fromY = y;
5617                 DragPieceBegin(xPix, yPix);
5618             }
5619             return;
5620         }
5621         // ignore clicks on holdings
5622         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5623     }
5624
5625     if (clickType == Release && x == fromX && y == fromY) {
5626         DragPieceEnd(xPix, yPix);
5627         if (appData.animateDragging) {
5628             /* Undo animation damage if any */
5629             DrawPosition(FALSE, NULL);
5630         }
5631         if (second) {
5632             /* Second up/down in same square; just abort move */
5633             second = 0;
5634             fromX = fromY = -1;
5635             ClearHighlights();
5636             gotPremove = 0;
5637             ClearPremoveHighlights();
5638         } else {
5639             /* First upclick in same square; start click-click mode */
5640             SetHighlights(x, y, -1, -1);
5641         }
5642         return;
5643     }
5644
5645     /* we now have a different from- and (possibly off-board) to-square */
5646     /* Completed move */
5647     toX = x;
5648     toY = y;
5649     saveAnimate = appData.animate;
5650     if (clickType == Press) {
5651         /* Finish clickclick move */
5652         if (appData.animate || appData.highlightLastMove) {
5653             SetHighlights(fromX, fromY, toX, toY);
5654         } else {
5655             ClearHighlights();
5656         }
5657     } else {
5658         /* Finish drag move */
5659         if (appData.highlightLastMove) {
5660             SetHighlights(fromX, fromY, toX, toY);
5661         } else {
5662             ClearHighlights();
5663         }
5664         DragPieceEnd(xPix, yPix);
5665         /* Don't animate move and drag both */
5666         appData.animate = FALSE;
5667     }
5668
5669     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5670     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5671         ClearHighlights();
5672         fromX = fromY = -1;
5673         return;
5674     }
5675
5676     // off-board moves should not be highlighted
5677     if(x < 0 || x < 0) ClearHighlights();
5678
5679     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5680         SetHighlights(fromX, fromY, toX, toY);
5681         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5682             // [HGM] super: promotion to captured piece selected from holdings
5683             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5684             promotionChoice = TRUE;
5685             // kludge follows to temporarily execute move on display, without promoting yet
5686             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5687             boards[currentMove][toY][toX] = p;
5688             DrawPosition(FALSE, boards[currentMove]);
5689             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5690             boards[currentMove][toY][toX] = q;
5691             DisplayMessage("Click in holdings to choose piece", "");
5692             return;
5693         }
5694         PromotionPopUp();
5695     } else {
5696         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5697         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5698         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5699         fromX = fromY = -1;
5700     }
5701     appData.animate = saveAnimate;
5702     if (appData.animate || appData.animateDragging) {
5703         /* Undo animation damage if needed */
5704         DrawPosition(FALSE, NULL);
5705     }
5706 }
5707
5708 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5709 {
5710 //    char * hint = lastHint;
5711     FrontEndProgramStats stats;
5712
5713     stats.which = cps == &first ? 0 : 1;
5714     stats.depth = cpstats->depth;
5715     stats.nodes = cpstats->nodes;
5716     stats.score = cpstats->score;
5717     stats.time = cpstats->time;
5718     stats.pv = cpstats->movelist;
5719     stats.hint = lastHint;
5720     stats.an_move_index = 0;
5721     stats.an_move_count = 0;
5722
5723     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5724         stats.hint = cpstats->move_name;
5725         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5726         stats.an_move_count = cpstats->nr_moves;
5727     }
5728
5729     SetProgramStats( &stats );
5730 }
5731
5732 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5733 {   // [HGM] book: this routine intercepts moves to simulate book replies
5734     char *bookHit = NULL;
5735
5736     //first determine if the incoming move brings opponent into his book
5737     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5738         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5739     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5740     if(bookHit != NULL && !cps->bookSuspend) {
5741         // make sure opponent is not going to reply after receiving move to book position
5742         SendToProgram("force\n", cps);
5743         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5744     }
5745     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5746     // now arrange restart after book miss
5747     if(bookHit) {
5748         // after a book hit we never send 'go', and the code after the call to this routine
5749         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5750         char buf[MSG_SIZ];
5751         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5752         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5753         SendToProgram(buf, cps);
5754         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5755     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5756         SendToProgram("go\n", cps);
5757         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5758     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5759         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5760             SendToProgram("go\n", cps); 
5761         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5762     }
5763     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5764 }
5765
5766 char *savedMessage;
5767 ChessProgramState *savedState;
5768 void DeferredBookMove(void)
5769 {
5770         if(savedState->lastPing != savedState->lastPong)
5771                     ScheduleDelayedEvent(DeferredBookMove, 10);
5772         else
5773         HandleMachineMove(savedMessage, savedState);
5774 }
5775
5776 void
5777 HandleMachineMove(message, cps)
5778      char *message;
5779      ChessProgramState *cps;
5780 {
5781     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5782     char realname[MSG_SIZ];
5783     int fromX, fromY, toX, toY;
5784     ChessMove moveType;
5785     char promoChar;
5786     char *p;
5787     int machineWhite;
5788     char *bookHit;
5789
5790 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5791     /*
5792      * Kludge to ignore BEL characters
5793      */
5794     while (*message == '\007') message++;
5795
5796     /*
5797      * [HGM] engine debug message: ignore lines starting with '#' character
5798      */
5799     if(cps->debug && *message == '#') return;
5800
5801     /*
5802      * Look for book output
5803      */
5804     if (cps == &first && bookRequested) {
5805         if (message[0] == '\t' || message[0] == ' ') {
5806             /* Part of the book output is here; append it */
5807             strcat(bookOutput, message);
5808             strcat(bookOutput, "  \n");
5809             return;
5810         } else if (bookOutput[0] != NULLCHAR) {
5811             /* All of book output has arrived; display it */
5812             char *p = bookOutput;
5813             while (*p != NULLCHAR) {
5814                 if (*p == '\t') *p = ' ';
5815                 p++;
5816             }
5817             DisplayInformation(bookOutput);
5818             bookRequested = FALSE;
5819             /* Fall through to parse the current output */
5820         }
5821     }
5822
5823     /*
5824      * Look for machine move.
5825      */
5826     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5827         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5828     {
5829         /* This method is only useful on engines that support ping */
5830         if (cps->lastPing != cps->lastPong) {
5831           if (gameMode == BeginningOfGame) {
5832             /* Extra move from before last new; ignore */
5833             if (appData.debugMode) {
5834                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5835             }
5836           } else {
5837             if (appData.debugMode) {
5838                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5839                         cps->which, gameMode);
5840             }
5841
5842             SendToProgram("undo\n", cps);
5843           }
5844           return;
5845         }
5846
5847         switch (gameMode) {
5848           case BeginningOfGame:
5849             /* Extra move from before last reset; ignore */
5850             if (appData.debugMode) {
5851                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5852             }
5853             return;
5854
5855           case EndOfGame:
5856           case IcsIdle:
5857           default:
5858             /* Extra move after we tried to stop.  The mode test is
5859                not a reliable way of detecting this problem, but it's
5860                the best we can do on engines that don't support ping.
5861             */
5862             if (appData.debugMode) {
5863                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5864                         cps->which, gameMode);
5865             }
5866             SendToProgram("undo\n", cps);
5867             return;
5868
5869           case MachinePlaysWhite:
5870           case IcsPlayingWhite:
5871             machineWhite = TRUE;
5872             break;
5873
5874           case MachinePlaysBlack:
5875           case IcsPlayingBlack:
5876             machineWhite = FALSE;
5877             break;
5878
5879           case TwoMachinesPlay:
5880             machineWhite = (cps->twoMachinesColor[0] == 'w');
5881             break;
5882         }
5883         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5884             if (appData.debugMode) {
5885                 fprintf(debugFP,
5886                         "Ignoring move out of turn by %s, gameMode %d"
5887                         ", forwardMost %d\n",
5888                         cps->which, gameMode, forwardMostMove);
5889             }
5890             return;
5891         }
5892
5893     if (appData.debugMode) { int f = forwardMostMove;
5894         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5895                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5896     }
5897         if(cps->alphaRank) AlphaRank(machineMove, 4);
5898         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5899                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5900             /* Machine move could not be parsed; ignore it. */
5901             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5902                     machineMove, cps->which);
5903             DisplayError(buf1, 0);
5904             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5905                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5906             if (gameMode == TwoMachinesPlay) {
5907               GameEnds(machineWhite ? BlackWins : WhiteWins,
5908                        buf1, GE_XBOARD);
5909             }
5910             return;
5911         }
5912
5913         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5914         /* So we have to redo legality test with true e.p. status here,  */
5915         /* to make sure an illegal e.p. capture does not slip through,   */
5916         /* to cause a forfeit on a justified illegal-move complaint      */
5917         /* of the opponent.                                              */
5918         if( gameMode==TwoMachinesPlay && appData.testLegality
5919             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5920                                                               ) {
5921            ChessMove moveType;
5922            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5923                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5924                              fromY, fromX, toY, toX, promoChar);
5925             if (appData.debugMode) {
5926                 int i;
5927                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5928                     castlingRights[forwardMostMove][i], castlingRank[i]);
5929                 fprintf(debugFP, "castling rights\n");
5930             }
5931             if(moveType == IllegalMove) {
5932                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5933                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5934                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5935                            buf1, GE_XBOARD);
5936                 return;
5937            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5938            /* [HGM] Kludge to handle engines that send FRC-style castling
5939               when they shouldn't (like TSCP-Gothic) */
5940            switch(moveType) {
5941              case WhiteASideCastleFR:
5942              case BlackASideCastleFR:
5943                toX+=2;
5944                currentMoveString[2]++;
5945                break;
5946              case WhiteHSideCastleFR:
5947              case BlackHSideCastleFR:
5948                toX--;
5949                currentMoveString[2]--;
5950                break;
5951              default: ; // nothing to do, but suppresses warning of pedantic compilers
5952            }
5953         }
5954         hintRequested = FALSE;
5955         lastHint[0] = NULLCHAR;
5956         bookRequested = FALSE;
5957         /* Program may be pondering now */
5958         cps->maybeThinking = TRUE;
5959         if (cps->sendTime == 2) cps->sendTime = 1;
5960         if (cps->offeredDraw) cps->offeredDraw--;
5961
5962 #if ZIPPY
5963         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5964             first.initDone) {
5965           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5966           ics_user_moved = 1;
5967           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5968                 char buf[3*MSG_SIZ];
5969
5970                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5971                         programStats.score / 100.,
5972                         programStats.depth,
5973                         programStats.time / 100.,
5974                         (unsigned int)programStats.nodes,
5975                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5976                         programStats.movelist);
5977                 SendToICS(buf);
5978 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5979           }
5980         }
5981 #endif
5982         /* currentMoveString is set as a side-effect of ParseOneMove */
5983         strcpy(machineMove, currentMoveString);
5984         strcat(machineMove, "\n");
5985         strcpy(moveList[forwardMostMove], machineMove);
5986
5987         /* [AS] Save move info and clear stats for next move */
5988         pvInfoList[ forwardMostMove ].score = programStats.score;
5989         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5990         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5991         ClearProgramStats();
5992         thinkOutput[0] = NULLCHAR;
5993         hiddenThinkOutputState = 0;
5994
5995         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5996
5997         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5998         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5999             int count = 0;
6000
6001             while( count < adjudicateLossPlies ) {
6002                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6003
6004                 if( count & 1 ) {
6005                     score = -score; /* Flip score for winning side */
6006                 }
6007
6008                 if( score > adjudicateLossThreshold ) {
6009                     break;
6010                 }
6011
6012                 count++;
6013             }
6014
6015             if( count >= adjudicateLossPlies ) {
6016                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6017
6018                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6019                     "Xboard adjudication", 
6020                     GE_XBOARD );
6021
6022                 return;
6023             }
6024         }
6025
6026         if( gameMode == TwoMachinesPlay ) {
6027           // [HGM] some adjudications useful with buggy engines
6028             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6029           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6030
6031
6032             if( appData.testLegality )
6033             {   /* [HGM] Some more adjudications for obstinate engines */
6034                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6035                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6036                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6037                 static int moveCount = 6;
6038                 ChessMove result;
6039                 char *reason = NULL;
6040
6041                 /* Count what is on board. */
6042                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6043                 {   ChessSquare p = boards[forwardMostMove][i][j];
6044                     int m=i;
6045
6046                     switch((int) p)
6047                     {   /* count B,N,R and other of each side */
6048                         case WhiteKing:
6049                         case BlackKing:
6050                              NrK++; break; // [HGM] atomic: count Kings
6051                         case WhiteKnight:
6052                              NrWN++; break;
6053                         case WhiteBishop:
6054                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6055                              bishopsColor |= 1 << ((i^j)&1);
6056                              NrWB++; break;
6057                         case BlackKnight:
6058                              NrBN++; break;
6059                         case BlackBishop:
6060                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6061                              bishopsColor |= 1 << ((i^j)&1);
6062                              NrBB++; break;
6063                         case WhiteRook:
6064                              NrWR++; break;
6065                         case BlackRook:
6066                              NrBR++; break;
6067                         case WhiteQueen:
6068                              NrWQ++; break;
6069                         case BlackQueen:
6070                              NrBQ++; break;
6071                         case EmptySquare: 
6072                              break;
6073                         case BlackPawn:
6074                              m = 7-i;
6075                         case WhitePawn:
6076                              PawnAdvance += m; NrPawns++;
6077                     }
6078                     NrPieces += (p != EmptySquare);
6079                     NrW += ((int)p < (int)BlackPawn);
6080                     if(gameInfo.variant == VariantXiangqi && 
6081                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6082                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6083                         NrW -= ((int)p < (int)BlackPawn);
6084                     }
6085                 }
6086
6087                 /* Some material-based adjudications that have to be made before stalemate test */
6088                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6089                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6090                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6091                      if(appData.checkMates) {
6092                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6093                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6094                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6095                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6096                          return;
6097                      }
6098                 }
6099
6100                 /* Bare King in Shatranj (loses) or Losers (wins) */
6101                 if( NrW == 1 || NrPieces - NrW == 1) {
6102                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6103                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6104                      if(appData.checkMates) {
6105                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6106                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6107                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6108                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6109                          return;
6110                      }
6111                   } else
6112                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6113                   {    /* bare King */
6114                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6115                         if(appData.checkMates) {
6116                             /* but only adjudicate if adjudication enabled */
6117                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6118                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6120                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6121                             return;
6122                         }
6123                   }
6124                 } else bare = 1;
6125
6126
6127             // don't wait for engine to announce game end if we can judge ourselves
6128             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6129                                        castlingRights[forwardMostMove]) ) {
6130               case MT_CHECK:
6131                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6132                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6133                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6134                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6135                             checkCnt++;
6136                         if(checkCnt >= 2) {
6137                             reason = "Xboard adjudication: 3rd check";
6138                             epStatus[forwardMostMove] = EP_CHECKMATE;
6139                             break;
6140                         }
6141                     }
6142                 }
6143               case MT_NONE:
6144               default:
6145                 break;
6146               case MT_STALEMATE:
6147               case MT_STAINMATE:
6148                 reason = "Xboard adjudication: Stalemate";
6149                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6150                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6151                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6152                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6153                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6154                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6155                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6156                                                                         EP_CHECKMATE : EP_WINS);
6157                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6158                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6159                 }
6160                 break;
6161               case MT_CHECKMATE:
6162                 reason = "Xboard adjudication: Checkmate";
6163                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6164                 break;
6165             }
6166
6167                 switch(i = epStatus[forwardMostMove]) {
6168                     case EP_STALEMATE:
6169                         result = GameIsDrawn; break;
6170                     case EP_CHECKMATE:
6171                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6172                     case EP_WINS:
6173                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6174                     default:
6175                         result = (ChessMove) 0;
6176                 }
6177                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6178                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6179                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6180                     GameEnds( result, reason, GE_XBOARD );
6181                     return;
6182                 }
6183
6184                 /* Next absolutely insufficient mating material. */
6185                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6186                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6187                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6188                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6189                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6190
6191                      /* always flag draws, for judging claims */
6192                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6193
6194                      if(appData.materialDraws) {
6195                          /* but only adjudicate them if adjudication enabled */
6196                          SendToProgram("force\n", cps->other); // suppress reply
6197                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6198                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6199                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6200                          return;
6201                      }
6202                 }
6203
6204                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6205                 if(NrPieces == 4 && 
6206                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6207                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6208                    || NrWN==2 || NrBN==2     /* KNNK */
6209                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6210                   ) ) {
6211                      if(--moveCount < 0 && appData.trivialDraws)
6212                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6213                           SendToProgram("force\n", cps->other); // suppress reply
6214                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6215                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6216                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6217                           return;
6218                      }
6219                 } else moveCount = 6;
6220             }
6221           }
6222           
6223           if (appData.debugMode) { int i;
6224             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6225                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6226                     appData.drawRepeats);
6227             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6228               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6229             
6230           }
6231
6232                 /* Check for rep-draws */
6233                 count = 0;
6234                 for(k = forwardMostMove-2;
6235                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6236                         epStatus[k] < EP_UNKNOWN &&
6237                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6238                     k-=2)
6239                 {   int rights=0;
6240                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6241                         /* compare castling rights */
6242                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6243                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6244                                 rights++; /* King lost rights, while rook still had them */
6245                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6246                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6247                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6248                                    rights++; /* but at least one rook lost them */
6249                         }
6250                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6251                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6252                                 rights++; 
6253                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6254                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6255                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6256                                    rights++;
6257                         }
6258                         if( rights == 0 && ++count > appData.drawRepeats-2
6259                             && appData.drawRepeats > 1) {
6260                              /* adjudicate after user-specified nr of repeats */
6261                              SendToProgram("force\n", cps->other); // suppress reply
6262                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6263                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6264                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6265                                 // [HGM] xiangqi: check for forbidden perpetuals
6266                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6267                                 for(m=forwardMostMove; m>k; m-=2) {
6268                                     if(MateTest(boards[m], PosFlags(m), 
6269                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6270                                         ourPerpetual = 0; // the current mover did not always check
6271                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6272                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6273                                         hisPerpetual = 0; // the opponent did not always check
6274                                 }
6275                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6276                                                                         ourPerpetual, hisPerpetual);
6277                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6278                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6279                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6280                                     return;
6281                                 }
6282                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6283                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6284                                 // Now check for perpetual chases
6285                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6286                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6287                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6288                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6289                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6290                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6291                                         return;
6292                                     }
6293                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6294                                         break; // Abort repetition-checking loop.
6295                                 }
6296                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6297                              }
6298                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6299                              return;
6300                         }
6301                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6302                              epStatus[forwardMostMove] = EP_REP_DRAW;
6303                     }
6304                 }
6305
6306                 /* Now we test for 50-move draws. Determine ply count */
6307                 count = forwardMostMove;
6308                 /* look for last irreversble move */
6309                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6310                     count--;
6311                 /* if we hit starting position, add initial plies */
6312                 if( count == backwardMostMove )
6313                     count -= initialRulePlies;
6314                 count = forwardMostMove - count; 
6315                 if( count >= 100)
6316                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6317                          /* this is used to judge if draw claims are legal */
6318                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6319                          SendToProgram("force\n", cps->other); // suppress reply
6320                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6321                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6322                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6323                          return;
6324                 }
6325
6326                 /* if draw offer is pending, treat it as a draw claim
6327                  * when draw condition present, to allow engines a way to
6328                  * claim draws before making their move to avoid a race
6329                  * condition occurring after their move
6330                  */
6331                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6332                          char *p = NULL;
6333                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6334                              p = "Draw claim: 50-move rule";
6335                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6336                              p = "Draw claim: 3-fold repetition";
6337                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6338                              p = "Draw claim: insufficient mating material";
6339                          if( p != NULL ) {
6340                              SendToProgram("force\n", cps->other); // suppress reply
6341                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6342                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6343                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6344                              return;
6345                          }
6346                 }
6347
6348
6349                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6350                     SendToProgram("force\n", cps->other); // suppress reply
6351                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6352                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6353
6354                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6355
6356                     return;
6357                 }
6358         }
6359
6360         bookHit = NULL;
6361         if (gameMode == TwoMachinesPlay) {
6362             /* [HGM] relaying draw offers moved to after reception of move */
6363             /* and interpreting offer as claim if it brings draw condition */
6364             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6365                 SendToProgram("draw\n", cps->other);
6366             }
6367             if (cps->other->sendTime) {
6368                 SendTimeRemaining(cps->other,
6369                                   cps->other->twoMachinesColor[0] == 'w');
6370             }
6371             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6372             if (firstMove && !bookHit) {
6373                 firstMove = FALSE;
6374                 if (cps->other->useColors) {
6375                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6376                 }
6377                 SendToProgram("go\n", cps->other);
6378             }
6379             cps->other->maybeThinking = TRUE;
6380         }
6381
6382         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6383         
6384         if (!pausing && appData.ringBellAfterMoves) {
6385             RingBell();
6386         }
6387
6388         /* 
6389          * Reenable menu items that were disabled while
6390          * machine was thinking
6391          */
6392         if (gameMode != TwoMachinesPlay)
6393             SetUserThinkingEnables();
6394
6395         // [HGM] book: after book hit opponent has received move and is now in force mode
6396         // force the book reply into it, and then fake that it outputted this move by jumping
6397         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6398         if(bookHit) {
6399                 static char bookMove[MSG_SIZ]; // a bit generous?
6400
6401                 strcpy(bookMove, "move ");
6402                 strcat(bookMove, bookHit);
6403                 message = bookMove;
6404                 cps = cps->other;
6405                 programStats.nodes = programStats.depth = programStats.time = 
6406                 programStats.score = programStats.got_only_move = 0;
6407                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6408
6409                 if(cps->lastPing != cps->lastPong) {
6410                     savedMessage = message; // args for deferred call
6411                     savedState = cps;
6412                     ScheduleDelayedEvent(DeferredBookMove, 10);
6413                     return;
6414                 }
6415                 goto FakeBookMove;
6416         }
6417
6418         return;
6419     }
6420
6421     /* Set special modes for chess engines.  Later something general
6422      *  could be added here; for now there is just one kludge feature,
6423      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6424      *  when "xboard" is given as an interactive command.
6425      */
6426     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6427         cps->useSigint = FALSE;
6428         cps->useSigterm = FALSE;
6429     }
6430     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6431       ParseFeatures(message+8, cps);
6432       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6433     }
6434
6435     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6436      * want this, I was asked to put it in, and obliged.
6437      */
6438     if (!strncmp(message, "setboard ", 9)) {
6439         Board initial_position; int i;
6440
6441         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6442
6443         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6444             DisplayError(_("Bad FEN received from engine"), 0);
6445             return ;
6446         } else {
6447            Reset(TRUE, FALSE);
6448            CopyBoard(boards[0], initial_position);
6449            initialRulePlies = FENrulePlies;
6450            epStatus[0] = FENepStatus;
6451            for( i=0; i<nrCastlingRights; i++ )
6452                 castlingRights[0][i] = FENcastlingRights[i];
6453            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6454            else gameMode = MachinePlaysBlack;                 
6455            DrawPosition(FALSE, boards[currentMove]);
6456         }
6457         return;
6458     }
6459
6460     /*
6461      * Look for communication commands
6462      */
6463     if (!strncmp(message, "telluser ", 9)) {
6464         DisplayNote(message + 9);
6465         return;
6466     }
6467     if (!strncmp(message, "tellusererror ", 14)) {
6468         DisplayError(message + 14, 0);
6469         return;
6470     }
6471     if (!strncmp(message, "tellopponent ", 13)) {
6472       if (appData.icsActive) {
6473         if (loggedOn) {
6474           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6475           SendToICS(buf1);
6476         }
6477       } else {
6478         DisplayNote(message + 13);
6479       }
6480       return;
6481     }
6482     if (!strncmp(message, "tellothers ", 11)) {
6483       if (appData.icsActive) {
6484         if (loggedOn) {
6485           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6486           SendToICS(buf1);
6487         }
6488       }
6489       return;
6490     }
6491     if (!strncmp(message, "tellall ", 8)) {
6492       if (appData.icsActive) {
6493         if (loggedOn) {
6494           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6495           SendToICS(buf1);
6496         }
6497       } else {
6498         DisplayNote(message + 8);
6499       }
6500       return;
6501     }
6502     if (strncmp(message, "warning", 7) == 0) {
6503         /* Undocumented feature, use tellusererror in new code */
6504         DisplayError(message, 0);
6505         return;
6506     }
6507     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6508         strcpy(realname, cps->tidy);
6509         strcat(realname, " query");
6510         AskQuestion(realname, buf2, buf1, cps->pr);
6511         return;
6512     }
6513     /* Commands from the engine directly to ICS.  We don't allow these to be 
6514      *  sent until we are logged on. Crafty kibitzes have been known to 
6515      *  interfere with the login process.
6516      */
6517     if (loggedOn) {
6518         if (!strncmp(message, "tellics ", 8)) {
6519             SendToICS(message + 8);
6520             SendToICS("\n");
6521             return;
6522         }
6523         if (!strncmp(message, "tellicsnoalias ", 15)) {
6524             SendToICS(ics_prefix);
6525             SendToICS(message + 15);
6526             SendToICS("\n");
6527             return;
6528         }
6529         /* The following are for backward compatibility only */
6530         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6531             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6532             SendToICS(ics_prefix);
6533             SendToICS(message);
6534             SendToICS("\n");
6535             return;
6536         }
6537     }
6538     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6539         return;
6540     }
6541     /*
6542      * If the move is illegal, cancel it and redraw the board.
6543      * Also deal with other error cases.  Matching is rather loose
6544      * here to accommodate engines written before the spec.
6545      */
6546     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6547         strncmp(message, "Error", 5) == 0) {
6548         if (StrStr(message, "name") || 
6549             StrStr(message, "rating") || StrStr(message, "?") ||
6550             StrStr(message, "result") || StrStr(message, "board") ||
6551             StrStr(message, "bk") || StrStr(message, "computer") ||
6552             StrStr(message, "variant") || StrStr(message, "hint") ||
6553             StrStr(message, "random") || StrStr(message, "depth") ||
6554             StrStr(message, "accepted")) {
6555             return;
6556         }
6557         if (StrStr(message, "protover")) {
6558           /* Program is responding to input, so it's apparently done
6559              initializing, and this error message indicates it is
6560              protocol version 1.  So we don't need to wait any longer
6561              for it to initialize and send feature commands. */
6562           FeatureDone(cps, 1);
6563           cps->protocolVersion = 1;
6564           return;
6565         }
6566         cps->maybeThinking = FALSE;
6567
6568         if (StrStr(message, "draw")) {
6569             /* Program doesn't have "draw" command */
6570             cps->sendDrawOffers = 0;
6571             return;
6572         }
6573         if (cps->sendTime != 1 &&
6574             (StrStr(message, "time") || StrStr(message, "otim"))) {
6575           /* Program apparently doesn't have "time" or "otim" command */
6576           cps->sendTime = 0;
6577           return;
6578         }
6579         if (StrStr(message, "analyze")) {
6580             cps->analysisSupport = FALSE;
6581             cps->analyzing = FALSE;
6582             Reset(FALSE, TRUE);
6583             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6584             DisplayError(buf2, 0);
6585             return;
6586         }
6587         if (StrStr(message, "(no matching move)st")) {
6588           /* Special kludge for GNU Chess 4 only */
6589           cps->stKludge = TRUE;
6590           SendTimeControl(cps, movesPerSession, timeControl,
6591                           timeIncrement, appData.searchDepth,
6592                           searchTime);
6593           return;
6594         }
6595         if (StrStr(message, "(no matching move)sd")) {
6596           /* Special kludge for GNU Chess 4 only */
6597           cps->sdKludge = TRUE;
6598           SendTimeControl(cps, movesPerSession, timeControl,
6599                           timeIncrement, appData.searchDepth,
6600                           searchTime);
6601           return;
6602         }
6603         if (!StrStr(message, "llegal")) {
6604             return;
6605         }
6606         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6607             gameMode == IcsIdle) return;
6608         if (forwardMostMove <= backwardMostMove) return;
6609         if (pausing) PauseEvent();
6610       if(appData.forceIllegal) {
6611             // [HGM] illegal: machine refused move; force position after move into it
6612           SendToProgram("force\n", cps);
6613           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6614                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6615                 // when black is to move, while there might be nothing on a2 or black
6616                 // might already have the move. So send the board as if white has the move.
6617                 // But first we must change the stm of the engine, as it refused the last move
6618                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6619                 if(WhiteOnMove(forwardMostMove)) {
6620                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6621                     SendBoard(cps, forwardMostMove); // kludgeless board
6622                 } else {
6623                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6624                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6625                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6626                 }
6627           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6628             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6629                  gameMode == TwoMachinesPlay)
6630               SendToProgram("go\n", cps);
6631             return;
6632       } else
6633         if (gameMode == PlayFromGameFile) {
6634             /* Stop reading this game file */
6635             gameMode = EditGame;
6636             ModeHighlight();
6637         }
6638         currentMove = --forwardMostMove;
6639         DisplayMove(currentMove-1); /* before DisplayMoveError */
6640         SwitchClocks();
6641         DisplayBothClocks();
6642         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6643                 parseList[currentMove], cps->which);
6644         DisplayMoveError(buf1);
6645         DrawPosition(FALSE, boards[currentMove]);
6646
6647         /* [HGM] illegal-move claim should forfeit game when Xboard */
6648         /* only passes fully legal moves                            */
6649         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6650             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6651                                 "False illegal-move claim", GE_XBOARD );
6652         }
6653         return;
6654     }
6655     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6656         /* Program has a broken "time" command that
6657            outputs a string not ending in newline.
6658            Don't use it. */
6659         cps->sendTime = 0;
6660     }
6661     
6662     /*
6663      * If chess program startup fails, exit with an error message.
6664      * Attempts to recover here are futile.
6665      */
6666     if ((StrStr(message, "unknown host") != NULL)
6667         || (StrStr(message, "No remote directory") != NULL)
6668         || (StrStr(message, "not found") != NULL)
6669         || (StrStr(message, "No such file") != NULL)
6670         || (StrStr(message, "can't alloc") != NULL)
6671         || (StrStr(message, "Permission denied") != NULL)) {
6672
6673         cps->maybeThinking = FALSE;
6674         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6675                 cps->which, cps->program, cps->host, message);
6676         RemoveInputSource(cps->isr);
6677         DisplayFatalError(buf1, 0, 1);
6678         return;
6679     }
6680     
6681     /* 
6682      * Look for hint output
6683      */
6684     if (sscanf(message, "Hint: %s", buf1) == 1) {
6685         if (cps == &first && hintRequested) {
6686             hintRequested = FALSE;
6687             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6688                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6689                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6690                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6691                                     fromY, fromX, toY, toX, promoChar, buf1);
6692                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6693                 DisplayInformation(buf2);
6694             } else {
6695                 /* Hint move could not be parsed!? */
6696               snprintf(buf2, sizeof(buf2),
6697                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6698                         buf1, cps->which);
6699                 DisplayError(buf2, 0);
6700             }
6701         } else {
6702             strcpy(lastHint, buf1);
6703         }
6704         return;
6705     }
6706
6707     /*
6708      * Ignore other messages if game is not in progress
6709      */
6710     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6711         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6712
6713     /*
6714      * look for win, lose, draw, or draw offer
6715      */
6716     if (strncmp(message, "1-0", 3) == 0) {
6717         char *p, *q, *r = "";
6718         p = strchr(message, '{');
6719         if (p) {
6720             q = strchr(p, '}');
6721             if (q) {
6722                 *q = NULLCHAR;
6723                 r = p + 1;
6724             }
6725         }
6726         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6727         return;
6728     } else if (strncmp(message, "0-1", 3) == 0) {
6729         char *p, *q, *r = "";
6730         p = strchr(message, '{');
6731         if (p) {
6732             q = strchr(p, '}');
6733             if (q) {
6734                 *q = NULLCHAR;
6735                 r = p + 1;
6736             }
6737         }
6738         /* Kludge for Arasan 4.1 bug */
6739         if (strcmp(r, "Black resigns") == 0) {
6740             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6741             return;
6742         }
6743         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6744         return;
6745     } else if (strncmp(message, "1/2", 3) == 0) {
6746         char *p, *q, *r = "";
6747         p = strchr(message, '{');
6748         if (p) {
6749             q = strchr(p, '}');
6750             if (q) {
6751                 *q = NULLCHAR;
6752                 r = p + 1;
6753             }
6754         }
6755             
6756         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6757         return;
6758
6759     } else if (strncmp(message, "White resign", 12) == 0) {
6760         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6761         return;
6762     } else if (strncmp(message, "Black resign", 12) == 0) {
6763         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6764         return;
6765     } else if (strncmp(message, "White matches", 13) == 0 ||
6766                strncmp(message, "Black matches", 13) == 0   ) {
6767         /* [HGM] ignore GNUShogi noises */
6768         return;
6769     } else if (strncmp(message, "White", 5) == 0 &&
6770                message[5] != '(' &&
6771                StrStr(message, "Black") == NULL) {
6772         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6773         return;
6774     } else if (strncmp(message, "Black", 5) == 0 &&
6775                message[5] != '(') {
6776         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6777         return;
6778     } else if (strcmp(message, "resign") == 0 ||
6779                strcmp(message, "computer resigns") == 0) {
6780         switch (gameMode) {
6781           case MachinePlaysBlack:
6782           case IcsPlayingBlack:
6783             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6784             break;
6785           case MachinePlaysWhite:
6786           case IcsPlayingWhite:
6787             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6788             break;
6789           case TwoMachinesPlay:
6790             if (cps->twoMachinesColor[0] == 'w')
6791               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6792             else
6793               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6794             break;
6795           default:
6796             /* can't happen */
6797             break;
6798         }
6799         return;
6800     } else if (strncmp(message, "opponent mates", 14) == 0) {
6801         switch (gameMode) {
6802           case MachinePlaysBlack:
6803           case IcsPlayingBlack:
6804             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6805             break;
6806           case MachinePlaysWhite:
6807           case IcsPlayingWhite:
6808             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6809             break;
6810           case TwoMachinesPlay:
6811             if (cps->twoMachinesColor[0] == 'w')
6812               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6813             else
6814               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6815             break;
6816           default:
6817             /* can't happen */
6818             break;
6819         }
6820         return;
6821     } else if (strncmp(message, "computer mates", 14) == 0) {
6822         switch (gameMode) {
6823           case MachinePlaysBlack:
6824           case IcsPlayingBlack:
6825             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6826             break;
6827           case MachinePlaysWhite:
6828           case IcsPlayingWhite:
6829             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6830             break;
6831           case TwoMachinesPlay:
6832             if (cps->twoMachinesColor[0] == 'w')
6833               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6834             else
6835               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6836             break;
6837           default:
6838             /* can't happen */
6839             break;
6840         }
6841         return;
6842     } else if (strncmp(message, "checkmate", 9) == 0) {
6843         if (WhiteOnMove(forwardMostMove)) {
6844             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6845         } else {
6846             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6847         }
6848         return;
6849     } else if (strstr(message, "Draw") != NULL ||
6850                strstr(message, "game is a draw") != NULL) {
6851         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6852         return;
6853     } else if (strstr(message, "offer") != NULL &&
6854                strstr(message, "draw") != NULL) {
6855 #if ZIPPY
6856         if (appData.zippyPlay && first.initDone) {
6857             /* Relay offer to ICS */
6858             SendToICS(ics_prefix);
6859             SendToICS("draw\n");
6860         }
6861 #endif
6862         cps->offeredDraw = 2; /* valid until this engine moves twice */
6863         if (gameMode == TwoMachinesPlay) {
6864             if (cps->other->offeredDraw) {
6865                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6866             /* [HGM] in two-machine mode we delay relaying draw offer      */
6867             /* until after we also have move, to see if it is really claim */
6868             }
6869         } else if (gameMode == MachinePlaysWhite ||
6870                    gameMode == MachinePlaysBlack) {
6871           if (userOfferedDraw) {
6872             DisplayInformation(_("Machine accepts your draw offer"));
6873             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6874           } else {
6875             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6876           }
6877         }
6878     }
6879
6880     
6881     /*
6882      * Look for thinking output
6883      */
6884     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6885           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6886                                 ) {
6887         int plylev, mvleft, mvtot, curscore, time;
6888         char mvname[MOVE_LEN];
6889         u64 nodes; // [DM]
6890         char plyext;
6891         int ignore = FALSE;
6892         int prefixHint = FALSE;
6893         mvname[0] = NULLCHAR;
6894
6895         switch (gameMode) {
6896           case MachinePlaysBlack:
6897           case IcsPlayingBlack:
6898             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6899             break;
6900           case MachinePlaysWhite:
6901           case IcsPlayingWhite:
6902             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6903             break;
6904           case AnalyzeMode:
6905           case AnalyzeFile:
6906             break;
6907           case IcsObserving: /* [DM] icsEngineAnalyze */
6908             if (!appData.icsEngineAnalyze) ignore = TRUE;
6909             break;
6910           case TwoMachinesPlay:
6911             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6912                 ignore = TRUE;
6913             }
6914             break;
6915           default:
6916             ignore = TRUE;
6917             break;
6918         }
6919
6920         if (!ignore) {
6921             buf1[0] = NULLCHAR;
6922             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6923                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6924
6925                 if (plyext != ' ' && plyext != '\t') {
6926                     time *= 100;
6927                 }
6928
6929                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6930                 if( cps->scoreIsAbsolute && 
6931                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6932                 {
6933                     curscore = -curscore;
6934                 }
6935
6936
6937                 programStats.depth = plylev;
6938                 programStats.nodes = nodes;
6939                 programStats.time = time;
6940                 programStats.score = curscore;
6941                 programStats.got_only_move = 0;
6942
6943                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6944                         int ticklen;
6945
6946                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6947                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6948                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6949                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6950                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6951                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6952                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6953                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6954                 }
6955
6956                 /* Buffer overflow protection */
6957                 if (buf1[0] != NULLCHAR) {
6958                     if (strlen(buf1) >= sizeof(programStats.movelist)
6959                         && appData.debugMode) {
6960                         fprintf(debugFP,
6961                                 "PV is too long; using the first %d bytes.\n",
6962                                 sizeof(programStats.movelist) - 1);
6963                     }
6964
6965                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6966                 } else {
6967                     sprintf(programStats.movelist, " no PV\n");
6968                 }
6969
6970                 if (programStats.seen_stat) {
6971                     programStats.ok_to_send = 1;
6972                 }
6973
6974                 if (strchr(programStats.movelist, '(') != NULL) {
6975                     programStats.line_is_book = 1;
6976                     programStats.nr_moves = 0;
6977                     programStats.moves_left = 0;
6978                 } else {
6979                     programStats.line_is_book = 0;
6980                 }
6981
6982                 SendProgramStatsToFrontend( cps, &programStats );
6983
6984                 /* 
6985                     [AS] Protect the thinkOutput buffer from overflow... this
6986                     is only useful if buf1 hasn't overflowed first!
6987                 */
6988                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6989                         plylev, 
6990                         (gameMode == TwoMachinesPlay ?
6991                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6992                         ((double) curscore) / 100.0,
6993                         prefixHint ? lastHint : "",
6994                         prefixHint ? " " : "" );
6995
6996                 if( buf1[0] != NULLCHAR ) {
6997                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6998
6999                     if( strlen(buf1) > max_len ) {
7000                         if( appData.debugMode) {
7001                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7002                         }
7003                         buf1[max_len+1] = '\0';
7004                     }
7005
7006                     strcat( thinkOutput, buf1 );
7007                 }
7008
7009                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7010                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7011                     DisplayMove(currentMove - 1);
7012                 }
7013                 return;
7014
7015             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7016                 /* crafty (9.25+) says "(only move) <move>"
7017                  * if there is only 1 legal move
7018                  */
7019                 sscanf(p, "(only move) %s", buf1);
7020                 sprintf(thinkOutput, "%s (only move)", buf1);
7021                 sprintf(programStats.movelist, "%s (only move)", buf1);
7022                 programStats.depth = 1;
7023                 programStats.nr_moves = 1;
7024                 programStats.moves_left = 1;
7025                 programStats.nodes = 1;
7026                 programStats.time = 1;
7027                 programStats.got_only_move = 1;
7028
7029                 /* Not really, but we also use this member to
7030                    mean "line isn't going to change" (Crafty
7031                    isn't searching, so stats won't change) */
7032                 programStats.line_is_book = 1;
7033
7034                 SendProgramStatsToFrontend( cps, &programStats );
7035                 
7036                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7037                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7038                     DisplayMove(currentMove - 1);
7039                 }
7040                 return;
7041             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7042                               &time, &nodes, &plylev, &mvleft,
7043                               &mvtot, mvname) >= 5) {
7044                 /* The stat01: line is from Crafty (9.29+) in response
7045                    to the "." command */
7046                 programStats.seen_stat = 1;
7047                 cps->maybeThinking = TRUE;
7048
7049                 if (programStats.got_only_move || !appData.periodicUpdates)
7050                   return;
7051
7052                 programStats.depth = plylev;
7053                 programStats.time = time;
7054                 programStats.nodes = nodes;
7055                 programStats.moves_left = mvleft;
7056                 programStats.nr_moves = mvtot;
7057                 strcpy(programStats.move_name, mvname);
7058                 programStats.ok_to_send = 1;
7059                 programStats.movelist[0] = '\0';
7060
7061                 SendProgramStatsToFrontend( cps, &programStats );
7062
7063                 return;
7064
7065             } else if (strncmp(message,"++",2) == 0) {
7066                 /* Crafty 9.29+ outputs this */
7067                 programStats.got_fail = 2;
7068                 return;
7069
7070             } else if (strncmp(message,"--",2) == 0) {
7071                 /* Crafty 9.29+ outputs this */
7072                 programStats.got_fail = 1;
7073                 return;
7074
7075             } else if (thinkOutput[0] != NULLCHAR &&
7076                        strncmp(message, "    ", 4) == 0) {
7077                 unsigned message_len;
7078
7079                 p = message;
7080                 while (*p && *p == ' ') p++;
7081
7082                 message_len = strlen( p );
7083
7084                 /* [AS] Avoid buffer overflow */
7085                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7086                     strcat(thinkOutput, " ");
7087                     strcat(thinkOutput, p);
7088                 }
7089
7090                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7091                     strcat(programStats.movelist, " ");
7092                     strcat(programStats.movelist, p);
7093                 }
7094
7095                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7096                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7097                     DisplayMove(currentMove - 1);
7098                 }
7099                 return;
7100             }
7101         }
7102         else {
7103             buf1[0] = NULLCHAR;
7104
7105             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7106                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7107             {
7108                 ChessProgramStats cpstats;
7109
7110                 if (plyext != ' ' && plyext != '\t') {
7111                     time *= 100;
7112                 }
7113
7114                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7115                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7116                     curscore = -curscore;
7117                 }
7118
7119                 cpstats.depth = plylev;
7120                 cpstats.nodes = nodes;
7121                 cpstats.time = time;
7122                 cpstats.score = curscore;
7123                 cpstats.got_only_move = 0;
7124                 cpstats.movelist[0] = '\0';
7125
7126                 if (buf1[0] != NULLCHAR) {
7127                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7128                 }
7129
7130                 cpstats.ok_to_send = 0;
7131                 cpstats.line_is_book = 0;
7132                 cpstats.nr_moves = 0;
7133                 cpstats.moves_left = 0;
7134
7135                 SendProgramStatsToFrontend( cps, &cpstats );
7136             }
7137         }
7138     }
7139 }
7140
7141
7142 /* Parse a game score from the character string "game", and
7143    record it as the history of the current game.  The game
7144    score is NOT assumed to start from the standard position. 
7145    The display is not updated in any way.
7146    */
7147 void
7148 ParseGameHistory(game)
7149      char *game;
7150 {
7151     ChessMove moveType;
7152     int fromX, fromY, toX, toY, boardIndex;
7153     char promoChar;
7154     char *p, *q;
7155     char buf[MSG_SIZ];
7156
7157     if (appData.debugMode)
7158       fprintf(debugFP, "Parsing game history: %s\n", game);
7159
7160     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7161     gameInfo.site = StrSave(appData.icsHost);
7162     gameInfo.date = PGNDate();
7163     gameInfo.round = StrSave("-");
7164
7165     /* Parse out names of players */
7166     while (*game == ' ') game++;
7167     p = buf;
7168     while (*game != ' ') *p++ = *game++;
7169     *p = NULLCHAR;
7170     gameInfo.white = StrSave(buf);
7171     while (*game == ' ') game++;
7172     p = buf;
7173     while (*game != ' ' && *game != '\n') *p++ = *game++;
7174     *p = NULLCHAR;
7175     gameInfo.black = StrSave(buf);
7176
7177     /* Parse moves */
7178     boardIndex = blackPlaysFirst ? 1 : 0;
7179     yynewstr(game);
7180     for (;;) {
7181         yyboardindex = boardIndex;
7182         moveType = (ChessMove) yylex();
7183         switch (moveType) {
7184           case IllegalMove:             /* maybe suicide chess, etc. */
7185   if (appData.debugMode) {
7186     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7187     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7188     setbuf(debugFP, NULL);
7189   }
7190           case WhitePromotionChancellor:
7191           case BlackPromotionChancellor:
7192           case WhitePromotionArchbishop:
7193           case BlackPromotionArchbishop:
7194           case WhitePromotionQueen:
7195           case BlackPromotionQueen:
7196           case WhitePromotionRook:
7197           case BlackPromotionRook:
7198           case WhitePromotionBishop:
7199           case BlackPromotionBishop:
7200           case WhitePromotionKnight:
7201           case BlackPromotionKnight:
7202           case WhitePromotionKing:
7203           case BlackPromotionKing:
7204           case NormalMove:
7205           case WhiteCapturesEnPassant:
7206           case BlackCapturesEnPassant:
7207           case WhiteKingSideCastle:
7208           case WhiteQueenSideCastle:
7209           case BlackKingSideCastle:
7210           case BlackQueenSideCastle:
7211           case WhiteKingSideCastleWild:
7212           case WhiteQueenSideCastleWild:
7213           case BlackKingSideCastleWild:
7214           case BlackQueenSideCastleWild:
7215           /* PUSH Fabien */
7216           case WhiteHSideCastleFR:
7217           case WhiteASideCastleFR:
7218           case BlackHSideCastleFR:
7219           case BlackASideCastleFR:
7220           /* POP Fabien */
7221             fromX = currentMoveString[0] - AAA;
7222             fromY = currentMoveString[1] - ONE;
7223             toX = currentMoveString[2] - AAA;
7224             toY = currentMoveString[3] - ONE;
7225             promoChar = currentMoveString[4];
7226             break;
7227           case WhiteDrop:
7228           case BlackDrop:
7229             fromX = moveType == WhiteDrop ?
7230               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7231             (int) CharToPiece(ToLower(currentMoveString[0]));
7232             fromY = DROP_RANK;
7233             toX = currentMoveString[2] - AAA;
7234             toY = currentMoveString[3] - ONE;
7235             promoChar = NULLCHAR;
7236             break;
7237           case AmbiguousMove:
7238             /* bug? */
7239             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7240   if (appData.debugMode) {
7241     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7242     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7243     setbuf(debugFP, NULL);
7244   }
7245             DisplayError(buf, 0);
7246             return;
7247           case ImpossibleMove:
7248             /* bug? */
7249             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7250   if (appData.debugMode) {
7251     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7252     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7253     setbuf(debugFP, NULL);
7254   }
7255             DisplayError(buf, 0);
7256             return;
7257           case (ChessMove) 0:   /* end of file */
7258             if (boardIndex < backwardMostMove) {
7259                 /* Oops, gap.  How did that happen? */
7260                 DisplayError(_("Gap in move list"), 0);
7261                 return;
7262             }
7263             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7264             if (boardIndex > forwardMostMove) {
7265                 forwardMostMove = boardIndex;
7266             }
7267             return;
7268           case ElapsedTime:
7269             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7270                 strcat(parseList[boardIndex-1], " ");
7271                 strcat(parseList[boardIndex-1], yy_text);
7272             }
7273             continue;
7274           case Comment:
7275           case PGNTag:
7276           case NAG:
7277           default:
7278             /* ignore */
7279             continue;
7280           case WhiteWins:
7281           case BlackWins:
7282           case GameIsDrawn:
7283           case GameUnfinished:
7284             if (gameMode == IcsExamining) {
7285                 if (boardIndex < backwardMostMove) {
7286                     /* Oops, gap.  How did that happen? */
7287                     return;
7288                 }
7289                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7290                 return;
7291             }
7292             gameInfo.result = moveType;
7293             p = strchr(yy_text, '{');
7294             if (p == NULL) p = strchr(yy_text, '(');
7295             if (p == NULL) {
7296                 p = yy_text;
7297                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7298             } else {
7299                 q = strchr(p, *p == '{' ? '}' : ')');
7300                 if (q != NULL) *q = NULLCHAR;
7301                 p++;
7302             }
7303             gameInfo.resultDetails = StrSave(p);
7304             continue;
7305         }
7306         if (boardIndex >= forwardMostMove &&
7307             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7308             backwardMostMove = blackPlaysFirst ? 1 : 0;
7309             return;
7310         }
7311         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7312                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7313                                  parseList[boardIndex]);
7314         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7315         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7316         /* currentMoveString is set as a side-effect of yylex */
7317         strcpy(moveList[boardIndex], currentMoveString);
7318         strcat(moveList[boardIndex], "\n");
7319         boardIndex++;
7320         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7321                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7322         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7323                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7324           case MT_NONE:
7325           case MT_STALEMATE:
7326           default:
7327             break;
7328           case MT_CHECK:
7329             if(gameInfo.variant != VariantShogi)
7330                 strcat(parseList[boardIndex - 1], "+");
7331             break;
7332           case MT_CHECKMATE:
7333           case MT_STAINMATE:
7334             strcat(parseList[boardIndex - 1], "#");
7335             break;
7336         }
7337     }
7338 }
7339
7340
7341 /* Apply a move to the given board  */
7342 void
7343 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7344      int fromX, fromY, toX, toY;
7345      int promoChar;
7346      Board board;
7347      char *castling;
7348      char *ep;
7349 {
7350   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7351
7352     /* [HGM] compute & store e.p. status and castling rights for new position */
7353     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7354     { int i;
7355
7356       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7357       oldEP = *ep;
7358       *ep = EP_NONE;
7359
7360       if( board[toY][toX] != EmptySquare ) 
7361            *ep = EP_CAPTURE;  
7362
7363       if( board[fromY][fromX] == WhitePawn ) {
7364            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7365                *ep = EP_PAWN_MOVE;
7366            if( toY-fromY==2) {
7367                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7368                         gameInfo.variant != VariantBerolina || toX < fromX)
7369                       *ep = toX | berolina;
7370                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7371                         gameInfo.variant != VariantBerolina || toX > fromX) 
7372                       *ep = toX;
7373            }
7374       } else 
7375       if( board[fromY][fromX] == BlackPawn ) {
7376            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7377                *ep = EP_PAWN_MOVE; 
7378            if( toY-fromY== -2) {
7379                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7380                         gameInfo.variant != VariantBerolina || toX < fromX)
7381                       *ep = toX | berolina;
7382                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7383                         gameInfo.variant != VariantBerolina || toX > fromX) 
7384                       *ep = toX;
7385            }
7386        }
7387
7388        for(i=0; i<nrCastlingRights; i++) {
7389            if(castling[i] == fromX && castlingRank[i] == fromY ||
7390               castling[i] == toX   && castlingRank[i] == toY   
7391              ) castling[i] = -1; // revoke for moved or captured piece
7392        }
7393
7394     }
7395
7396   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7397   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7398        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7399          
7400   if (fromX == toX && fromY == toY) return;
7401
7402   if (fromY == DROP_RANK) {
7403         /* must be first */
7404         piece = board[toY][toX] = (ChessSquare) fromX;
7405   } else {
7406      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7407      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7408      if(gameInfo.variant == VariantKnightmate)
7409          king += (int) WhiteUnicorn - (int) WhiteKing;
7410
7411     /* Code added by Tord: */
7412     /* FRC castling assumed when king captures friendly rook. */
7413     if (board[fromY][fromX] == WhiteKing &&
7414              board[toY][toX] == WhiteRook) {
7415       board[fromY][fromX] = EmptySquare;
7416       board[toY][toX] = EmptySquare;
7417       if(toX > fromX) {
7418         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7419       } else {
7420         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7421       }
7422     } else if (board[fromY][fromX] == BlackKing &&
7423                board[toY][toX] == BlackRook) {
7424       board[fromY][fromX] = EmptySquare;
7425       board[toY][toX] = EmptySquare;
7426       if(toX > fromX) {
7427         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7428       } else {
7429         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7430       }
7431     /* End of code added by Tord */
7432
7433     } else if (board[fromY][fromX] == king
7434         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7435         && toY == fromY && toX > fromX+1) {
7436         board[fromY][fromX] = EmptySquare;
7437         board[toY][toX] = king;
7438         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7439         board[fromY][BOARD_RGHT-1] = EmptySquare;
7440     } else if (board[fromY][fromX] == king
7441         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7442                && toY == fromY && toX < fromX-1) {
7443         board[fromY][fromX] = EmptySquare;
7444         board[toY][toX] = king;
7445         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7446         board[fromY][BOARD_LEFT] = EmptySquare;
7447     } else if (board[fromY][fromX] == WhitePawn
7448                && toY == BOARD_HEIGHT-1
7449                && gameInfo.variant != VariantXiangqi
7450                ) {
7451         /* white pawn promotion */
7452         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7453         if (board[toY][toX] == EmptySquare) {
7454             board[toY][toX] = WhiteQueen;
7455         }
7456         if(gameInfo.variant==VariantBughouse ||
7457            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7458             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7459         board[fromY][fromX] = EmptySquare;
7460     } else if ((fromY == BOARD_HEIGHT-4)
7461                && (toX != fromX)
7462                && gameInfo.variant != VariantXiangqi
7463                && gameInfo.variant != VariantBerolina
7464                && (board[fromY][fromX] == WhitePawn)
7465                && (board[toY][toX] == EmptySquare)) {
7466         board[fromY][fromX] = EmptySquare;
7467         board[toY][toX] = WhitePawn;
7468         captured = board[toY - 1][toX];
7469         board[toY - 1][toX] = EmptySquare;
7470     } else if ((fromY == BOARD_HEIGHT-4)
7471                && (toX == fromX)
7472                && gameInfo.variant == VariantBerolina
7473                && (board[fromY][fromX] == WhitePawn)
7474                && (board[toY][toX] == EmptySquare)) {
7475         board[fromY][fromX] = EmptySquare;
7476         board[toY][toX] = WhitePawn;
7477         if(oldEP & EP_BEROLIN_A) {
7478                 captured = board[fromY][fromX-1];
7479                 board[fromY][fromX-1] = EmptySquare;
7480         }else{  captured = board[fromY][fromX+1];
7481                 board[fromY][fromX+1] = EmptySquare;
7482         }
7483     } else if (board[fromY][fromX] == king
7484         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7485                && toY == fromY && toX > fromX+1) {
7486         board[fromY][fromX] = EmptySquare;
7487         board[toY][toX] = king;
7488         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7489         board[fromY][BOARD_RGHT-1] = EmptySquare;
7490     } else if (board[fromY][fromX] == king
7491         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7492                && toY == fromY && toX < fromX-1) {
7493         board[fromY][fromX] = EmptySquare;
7494         board[toY][toX] = king;
7495         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7496         board[fromY][BOARD_LEFT] = EmptySquare;
7497     } else if (fromY == 7 && fromX == 3
7498                && board[fromY][fromX] == BlackKing
7499                && toY == 7 && toX == 5) {
7500         board[fromY][fromX] = EmptySquare;
7501         board[toY][toX] = BlackKing;
7502         board[fromY][7] = EmptySquare;
7503         board[toY][4] = BlackRook;
7504     } else if (fromY == 7 && fromX == 3
7505                && board[fromY][fromX] == BlackKing
7506                && toY == 7 && toX == 1) {
7507         board[fromY][fromX] = EmptySquare;
7508         board[toY][toX] = BlackKing;
7509         board[fromY][0] = EmptySquare;
7510         board[toY][2] = BlackRook;
7511     } else if (board[fromY][fromX] == BlackPawn
7512                && toY == 0
7513                && gameInfo.variant != VariantXiangqi
7514                ) {
7515         /* black pawn promotion */
7516         board[0][toX] = CharToPiece(ToLower(promoChar));
7517         if (board[0][toX] == EmptySquare) {
7518             board[0][toX] = BlackQueen;
7519         }
7520         if(gameInfo.variant==VariantBughouse ||
7521            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7522             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7523         board[fromY][fromX] = EmptySquare;
7524     } else if ((fromY == 3)
7525                && (toX != fromX)
7526                && gameInfo.variant != VariantXiangqi
7527                && gameInfo.variant != VariantBerolina
7528                && (board[fromY][fromX] == BlackPawn)
7529                && (board[toY][toX] == EmptySquare)) {
7530         board[fromY][fromX] = EmptySquare;
7531         board[toY][toX] = BlackPawn;
7532         captured = board[toY + 1][toX];
7533         board[toY + 1][toX] = EmptySquare;
7534     } else if ((fromY == 3)
7535                && (toX == fromX)
7536                && gameInfo.variant == VariantBerolina
7537                && (board[fromY][fromX] == BlackPawn)
7538                && (board[toY][toX] == EmptySquare)) {
7539         board[fromY][fromX] = EmptySquare;
7540         board[toY][toX] = BlackPawn;
7541         if(oldEP & EP_BEROLIN_A) {
7542                 captured = board[fromY][fromX-1];
7543                 board[fromY][fromX-1] = EmptySquare;
7544         }else{  captured = board[fromY][fromX+1];
7545                 board[fromY][fromX+1] = EmptySquare;
7546         }
7547     } else {
7548         board[toY][toX] = board[fromY][fromX];
7549         board[fromY][fromX] = EmptySquare;
7550     }
7551
7552     /* [HGM] now we promote for Shogi, if needed */
7553     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7554         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7555   }
7556
7557     if (gameInfo.holdingsWidth != 0) {
7558
7559       /* !!A lot more code needs to be written to support holdings  */
7560       /* [HGM] OK, so I have written it. Holdings are stored in the */
7561       /* penultimate board files, so they are automaticlly stored   */
7562       /* in the game history.                                       */
7563       if (fromY == DROP_RANK) {
7564         /* Delete from holdings, by decreasing count */
7565         /* and erasing image if necessary            */
7566         p = (int) fromX;
7567         if(p < (int) BlackPawn) { /* white drop */
7568              p -= (int)WhitePawn;
7569                  p = PieceToNumber((ChessSquare)p);
7570              if(p >= gameInfo.holdingsSize) p = 0;
7571              if(--board[p][BOARD_WIDTH-2] <= 0)
7572                   board[p][BOARD_WIDTH-1] = EmptySquare;
7573              if((int)board[p][BOARD_WIDTH-2] < 0)
7574                         board[p][BOARD_WIDTH-2] = 0;
7575         } else {                  /* black drop */
7576              p -= (int)BlackPawn;
7577                  p = PieceToNumber((ChessSquare)p);
7578              if(p >= gameInfo.holdingsSize) p = 0;
7579              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7580                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7581              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7582                         board[BOARD_HEIGHT-1-p][1] = 0;
7583         }
7584       }
7585       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7586           && gameInfo.variant != VariantBughouse        ) {
7587         /* [HGM] holdings: Add to holdings, if holdings exist */
7588         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7589                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7590                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7591         }
7592         p = (int) captured;
7593         if (p >= (int) BlackPawn) {
7594           p -= (int)BlackPawn;
7595           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7596                   /* in Shogi restore piece to its original  first */
7597                   captured = (ChessSquare) (DEMOTED captured);
7598                   p = DEMOTED p;
7599           }
7600           p = PieceToNumber((ChessSquare)p);
7601           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7602           board[p][BOARD_WIDTH-2]++;
7603           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7604         } else {
7605           p -= (int)WhitePawn;
7606           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7607                   captured = (ChessSquare) (DEMOTED captured);
7608                   p = DEMOTED p;
7609           }
7610           p = PieceToNumber((ChessSquare)p);
7611           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7612           board[BOARD_HEIGHT-1-p][1]++;
7613           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7614         }
7615       }
7616     } else if (gameInfo.variant == VariantAtomic) {
7617       if (captured != EmptySquare) {
7618         int y, x;
7619         for (y = toY-1; y <= toY+1; y++) {
7620           for (x = toX-1; x <= toX+1; x++) {
7621             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7622                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7623               board[y][x] = EmptySquare;
7624             }
7625           }
7626         }
7627         board[toY][toX] = EmptySquare;
7628       }
7629     }
7630     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7631         /* [HGM] Shogi promotions */
7632         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7633     }
7634
7635     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7636                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7637         // [HGM] superchess: take promotion piece out of holdings
7638         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7639         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7640             if(!--board[k][BOARD_WIDTH-2])
7641                 board[k][BOARD_WIDTH-1] = EmptySquare;
7642         } else {
7643             if(!--board[BOARD_HEIGHT-1-k][1])
7644                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7645         }
7646     }
7647
7648 }
7649
7650 /* Updates forwardMostMove */
7651 void
7652 MakeMove(fromX, fromY, toX, toY, promoChar)
7653      int fromX, fromY, toX, toY;
7654      int promoChar;
7655 {
7656 //    forwardMostMove++; // [HGM] bare: moved downstream
7657
7658     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7659         int timeLeft; static int lastLoadFlag=0; int king, piece;
7660         piece = boards[forwardMostMove][fromY][fromX];
7661         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7662         if(gameInfo.variant == VariantKnightmate)
7663             king += (int) WhiteUnicorn - (int) WhiteKing;
7664         if(forwardMostMove == 0) {
7665             if(blackPlaysFirst) 
7666                 fprintf(serverMoves, "%s;", second.tidy);
7667             fprintf(serverMoves, "%s;", first.tidy);
7668             if(!blackPlaysFirst) 
7669                 fprintf(serverMoves, "%s;", second.tidy);
7670         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7671         lastLoadFlag = loadFlag;
7672         // print base move
7673         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7674         // print castling suffix
7675         if( toY == fromY && piece == king ) {
7676             if(toX-fromX > 1)
7677                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7678             if(fromX-toX >1)
7679                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7680         }
7681         // e.p. suffix
7682         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7683              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7684              boards[forwardMostMove][toY][toX] == EmptySquare
7685              && fromX != toX )
7686                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7687         // promotion suffix
7688         if(promoChar != NULLCHAR)
7689                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7690         if(!loadFlag) {
7691             fprintf(serverMoves, "/%d/%d",
7692                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7693             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7694             else                      timeLeft = blackTimeRemaining/1000;
7695             fprintf(serverMoves, "/%d", timeLeft);
7696         }
7697         fflush(serverMoves);
7698     }
7699
7700     if (forwardMostMove+1 >= MAX_MOVES) {
7701       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7702                         0, 1);
7703       return;
7704     }
7705     if (commentList[forwardMostMove+1] != NULL) {
7706         free(commentList[forwardMostMove+1]);
7707         commentList[forwardMostMove+1] = NULL;
7708     }
7709     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7710     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7711     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7712                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7713     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7714     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7715     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7716     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7717     gameInfo.result = GameUnfinished;
7718     if (gameInfo.resultDetails != NULL) {
7719         free(gameInfo.resultDetails);
7720         gameInfo.resultDetails = NULL;
7721     }
7722     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7723                               moveList[forwardMostMove - 1]);
7724     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7725                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7726                              fromY, fromX, toY, toX, promoChar,
7727                              parseList[forwardMostMove - 1]);
7728     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7729                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7730                             castlingRights[forwardMostMove]) ) {
7731       case MT_NONE:
7732       case MT_STALEMATE:
7733       default:
7734         break;
7735       case MT_CHECK:
7736         if(gameInfo.variant != VariantShogi)
7737             strcat(parseList[forwardMostMove - 1], "+");
7738         break;
7739       case MT_CHECKMATE:
7740       case MT_STAINMATE:
7741         strcat(parseList[forwardMostMove - 1], "#");
7742         break;
7743     }
7744     if (appData.debugMode) {
7745         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7746     }
7747
7748 }
7749
7750 /* Updates currentMove if not pausing */
7751 void
7752 ShowMove(fromX, fromY, toX, toY)
7753 {
7754     int instant = (gameMode == PlayFromGameFile) ?
7755         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7756     if(appData.noGUI) return;
7757     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7758         if (!instant) {
7759             if (forwardMostMove == currentMove + 1) {
7760                 AnimateMove(boards[forwardMostMove - 1],
7761                             fromX, fromY, toX, toY);
7762             }
7763             if (appData.highlightLastMove) {
7764                 SetHighlights(fromX, fromY, toX, toY);
7765             }
7766         }
7767         currentMove = forwardMostMove;
7768     }
7769
7770     if (instant) return;
7771
7772     DisplayMove(currentMove - 1);
7773     DrawPosition(FALSE, boards[currentMove]);
7774     DisplayBothClocks();
7775     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7776 }
7777
7778 void SendEgtPath(ChessProgramState *cps)
7779 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7780         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7781
7782         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7783
7784         while(*p) {
7785             char c, *q = name+1, *r, *s;
7786
7787             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7788             while(*p && *p != ',') *q++ = *p++;
7789             *q++ = ':'; *q = 0;
7790             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7791                 strcmp(name, ",nalimov:") == 0 ) {
7792                 // take nalimov path from the menu-changeable option first, if it is defined
7793                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7794                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7795             } else
7796             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7797                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7798                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7799                 s = r = StrStr(s, ":") + 1; // beginning of path info
7800                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7801                 c = *r; *r = 0;             // temporarily null-terminate path info
7802                     *--q = 0;               // strip of trailig ':' from name
7803                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7804                 *r = c;
7805                 SendToProgram(buf,cps);     // send egtbpath command for this format
7806             }
7807             if(*p == ',') p++; // read away comma to position for next format name
7808         }
7809 }
7810
7811 void
7812 InitChessProgram(cps, setup)
7813      ChessProgramState *cps;
7814      int setup; /* [HGM] needed to setup FRC opening position */
7815 {
7816     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7817     if (appData.noChessProgram) return;
7818     hintRequested = FALSE;
7819     bookRequested = FALSE;
7820
7821     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7822     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7823     if(cps->memSize) { /* [HGM] memory */
7824         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7825         SendToProgram(buf, cps);
7826     }
7827     SendEgtPath(cps); /* [HGM] EGT */
7828     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7829         sprintf(buf, "cores %d\n", appData.smpCores);
7830         SendToProgram(buf, cps);
7831     }
7832
7833     SendToProgram(cps->initString, cps);
7834     if (gameInfo.variant != VariantNormal &&
7835         gameInfo.variant != VariantLoadable
7836         /* [HGM] also send variant if board size non-standard */
7837         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7838                                             ) {
7839       char *v = VariantName(gameInfo.variant);
7840       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7841         /* [HGM] in protocol 1 we have to assume all variants valid */
7842         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7843         DisplayFatalError(buf, 0, 1);
7844         return;
7845       }
7846
7847       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7848       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7849       if( gameInfo.variant == VariantXiangqi )
7850            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7851       if( gameInfo.variant == VariantShogi )
7852            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7853       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7854            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7855       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7856                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7857            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7858       if( gameInfo.variant == VariantCourier )
7859            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7860       if( gameInfo.variant == VariantSuper )
7861            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7862       if( gameInfo.variant == VariantGreat )
7863            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7864
7865       if(overruled) {
7866            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7867                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7868            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7869            if(StrStr(cps->variants, b) == NULL) { 
7870                // specific sized variant not known, check if general sizing allowed
7871                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7872                    if(StrStr(cps->variants, "boardsize") == NULL) {
7873                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7874                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7875                        DisplayFatalError(buf, 0, 1);
7876                        return;
7877                    }
7878                    /* [HGM] here we really should compare with the maximum supported board size */
7879                }
7880            }
7881       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7882       sprintf(buf, "variant %s\n", b);
7883       SendToProgram(buf, cps);
7884     }
7885     currentlyInitializedVariant = gameInfo.variant;
7886
7887     /* [HGM] send opening position in FRC to first engine */
7888     if(setup) {
7889           SendToProgram("force\n", cps);
7890           SendBoard(cps, 0);
7891           /* engine is now in force mode! Set flag to wake it up after first move. */
7892           setboardSpoiledMachineBlack = 1;
7893     }
7894
7895     if (cps->sendICS) {
7896       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7897       SendToProgram(buf, cps);
7898     }
7899     cps->maybeThinking = FALSE;
7900     cps->offeredDraw = 0;
7901     if (!appData.icsActive) {
7902         SendTimeControl(cps, movesPerSession, timeControl,
7903                         timeIncrement, appData.searchDepth,
7904                         searchTime);
7905     }
7906     if (appData.showThinking 
7907         // [HGM] thinking: four options require thinking output to be sent
7908         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7909                                 ) {
7910         SendToProgram("post\n", cps);
7911     }
7912     SendToProgram("hard\n", cps);
7913     if (!appData.ponderNextMove) {
7914         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7915            it without being sure what state we are in first.  "hard"
7916            is not a toggle, so that one is OK.
7917          */
7918         SendToProgram("easy\n", cps);
7919     }
7920     if (cps->usePing) {
7921       sprintf(buf, "ping %d\n", ++cps->lastPing);
7922       SendToProgram(buf, cps);
7923     }
7924     cps->initDone = TRUE;
7925 }   
7926
7927
7928 void
7929 StartChessProgram(cps)
7930      ChessProgramState *cps;
7931 {
7932     char buf[MSG_SIZ];
7933     int err;
7934
7935     if (appData.noChessProgram) return;
7936     cps->initDone = FALSE;
7937
7938     if (strcmp(cps->host, "localhost") == 0) {
7939         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7940     } else if (*appData.remoteShell == NULLCHAR) {
7941         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7942     } else {
7943         if (*appData.remoteUser == NULLCHAR) {
7944           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7945                     cps->program);
7946         } else {
7947           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7948                     cps->host, appData.remoteUser, cps->program);
7949         }
7950         err = StartChildProcess(buf, "", &cps->pr);
7951     }
7952     
7953     if (err != 0) {
7954         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7955         DisplayFatalError(buf, err, 1);
7956         cps->pr = NoProc;
7957         cps->isr = NULL;
7958         return;
7959     }
7960     
7961     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7962     if (cps->protocolVersion > 1) {
7963       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7964       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7965       cps->comboCnt = 0;  //                and values of combo boxes
7966       SendToProgram(buf, cps);
7967     } else {
7968       SendToProgram("xboard\n", cps);
7969     }
7970 }
7971
7972
7973 void
7974 TwoMachinesEventIfReady P((void))
7975 {
7976   if (first.lastPing != first.lastPong) {
7977     DisplayMessage("", _("Waiting for first chess program"));
7978     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7979     return;
7980   }
7981   if (second.lastPing != second.lastPong) {
7982     DisplayMessage("", _("Waiting for second chess program"));
7983     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7984     return;
7985   }
7986   ThawUI();
7987   TwoMachinesEvent();
7988 }
7989
7990 void
7991 NextMatchGame P((void))
7992 {
7993     int index; /* [HGM] autoinc: step load index during match */
7994     Reset(FALSE, TRUE);
7995     if (*appData.loadGameFile != NULLCHAR) {
7996         index = appData.loadGameIndex;
7997         if(index < 0) { // [HGM] autoinc
7998             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7999             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8000         } 
8001         LoadGameFromFile(appData.loadGameFile,
8002                          index,
8003                          appData.loadGameFile, FALSE);
8004     } else if (*appData.loadPositionFile != NULLCHAR) {
8005         index = appData.loadPositionIndex;
8006         if(index < 0) { // [HGM] autoinc
8007             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8008             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8009         } 
8010         LoadPositionFromFile(appData.loadPositionFile,
8011                              index,
8012                              appData.loadPositionFile);
8013     }
8014     TwoMachinesEventIfReady();
8015 }
8016
8017 void UserAdjudicationEvent( int result )
8018 {
8019     ChessMove gameResult = GameIsDrawn;
8020
8021     if( result > 0 ) {
8022         gameResult = WhiteWins;
8023     }
8024     else if( result < 0 ) {
8025         gameResult = BlackWins;
8026     }
8027
8028     if( gameMode == TwoMachinesPlay ) {
8029         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8030     }
8031 }
8032
8033
8034 // [HGM] save: calculate checksum of game to make games easily identifiable
8035 int StringCheckSum(char *s)
8036 {
8037         int i = 0;
8038         if(s==NULL) return 0;
8039         while(*s) i = i*259 + *s++;
8040         return i;
8041 }
8042
8043 int GameCheckSum()
8044 {
8045         int i, sum=0;
8046         for(i=backwardMostMove; i<forwardMostMove; i++) {
8047                 sum += pvInfoList[i].depth;
8048                 sum += StringCheckSum(parseList[i]);
8049                 sum += StringCheckSum(commentList[i]);
8050                 sum *= 261;
8051         }
8052         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8053         return sum + StringCheckSum(commentList[i]);
8054 } // end of save patch
8055
8056 void
8057 GameEnds(result, resultDetails, whosays)
8058      ChessMove result;
8059      char *resultDetails;
8060      int whosays;
8061 {
8062     GameMode nextGameMode;
8063     int isIcsGame;
8064     char buf[MSG_SIZ];
8065
8066     if(endingGame) return; /* [HGM] crash: forbid recursion */
8067     endingGame = 1;
8068
8069     if (appData.debugMode) {
8070       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8071               result, resultDetails ? resultDetails : "(null)", whosays);
8072     }
8073
8074     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8075         /* If we are playing on ICS, the server decides when the
8076            game is over, but the engine can offer to draw, claim 
8077            a draw, or resign. 
8078          */
8079 #if ZIPPY
8080         if (appData.zippyPlay && first.initDone) {
8081             if (result == GameIsDrawn) {
8082                 /* In case draw still needs to be claimed */
8083                 SendToICS(ics_prefix);
8084                 SendToICS("draw\n");
8085             } else if (StrCaseStr(resultDetails, "resign")) {
8086                 SendToICS(ics_prefix);
8087                 SendToICS("resign\n");
8088             }
8089         }
8090 #endif
8091         endingGame = 0; /* [HGM] crash */
8092         return;
8093     }
8094
8095     /* If we're loading the game from a file, stop */
8096     if (whosays == GE_FILE) {
8097       (void) StopLoadGameTimer();
8098       gameFileFP = NULL;
8099     }
8100
8101     /* Cancel draw offers */
8102     first.offeredDraw = second.offeredDraw = 0;
8103
8104     /* If this is an ICS game, only ICS can really say it's done;
8105        if not, anyone can. */
8106     isIcsGame = (gameMode == IcsPlayingWhite || 
8107                  gameMode == IcsPlayingBlack || 
8108                  gameMode == IcsObserving    || 
8109                  gameMode == IcsExamining);
8110
8111     if (!isIcsGame || whosays == GE_ICS) {
8112         /* OK -- not an ICS game, or ICS said it was done */
8113         StopClocks();
8114         if (!isIcsGame && !appData.noChessProgram) 
8115           SetUserThinkingEnables();
8116     
8117         /* [HGM] if a machine claims the game end we verify this claim */
8118         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8119             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8120                 char claimer;
8121                 ChessMove trueResult = (ChessMove) -1;
8122
8123                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8124                                             first.twoMachinesColor[0] :
8125                                             second.twoMachinesColor[0] ;
8126
8127                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8128                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8129                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8130                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8131                 } else
8132                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8133                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8134                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8135                 } else
8136                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8137                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8138                 }
8139
8140                 // now verify win claims, but not in drop games, as we don't understand those yet
8141                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8142                                                  || gameInfo.variant == VariantGreat) &&
8143                     (result == WhiteWins && claimer == 'w' ||
8144                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8145                       if (appData.debugMode) {
8146                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8147                                 result, epStatus[forwardMostMove], forwardMostMove);
8148                       }
8149                       if(result != trueResult) {
8150                               sprintf(buf, "False win claim: '%s'", resultDetails);
8151                               result = claimer == 'w' ? BlackWins : WhiteWins;
8152                               resultDetails = buf;
8153                       }
8154                 } else
8155                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8156                     && (forwardMostMove <= backwardMostMove ||
8157                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8158                         (claimer=='b')==(forwardMostMove&1))
8159                                                                                   ) {
8160                       /* [HGM] verify: draws that were not flagged are false claims */
8161                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8162                       result = claimer == 'w' ? BlackWins : WhiteWins;
8163                       resultDetails = buf;
8164                 }
8165                 /* (Claiming a loss is accepted no questions asked!) */
8166             }
8167             /* [HGM] bare: don't allow bare King to win */
8168             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8169                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8170                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8171                && result != GameIsDrawn)
8172             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8173                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8174                         int p = (int)boards[forwardMostMove][i][j] - color;
8175                         if(p >= 0 && p <= (int)WhiteKing) k++;
8176                 }
8177                 if (appData.debugMode) {
8178                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8179                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8180                 }
8181                 if(k <= 1) {
8182                         result = GameIsDrawn;
8183                         sprintf(buf, "%s but bare king", resultDetails);
8184                         resultDetails = buf;
8185                 }
8186             }
8187         }
8188
8189
8190         if(serverMoves != NULL && !loadFlag) { char c = '=';
8191             if(result==WhiteWins) c = '+';
8192             if(result==BlackWins) c = '-';
8193             if(resultDetails != NULL)
8194                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8195         }
8196         if (resultDetails != NULL) {
8197             gameInfo.result = result;
8198             gameInfo.resultDetails = StrSave(resultDetails);
8199
8200             /* display last move only if game was not loaded from file */
8201             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8202                 DisplayMove(currentMove - 1);
8203     
8204             if (forwardMostMove != 0) {
8205                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8206                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8207                                                                 ) {
8208                     if (*appData.saveGameFile != NULLCHAR) {
8209                         SaveGameToFile(appData.saveGameFile, TRUE);
8210                     } else if (appData.autoSaveGames) {
8211                         AutoSaveGame();
8212                     }
8213                     if (*appData.savePositionFile != NULLCHAR) {
8214                         SavePositionToFile(appData.savePositionFile);
8215                     }
8216                 }
8217             }
8218
8219             /* Tell program how game ended in case it is learning */
8220             /* [HGM] Moved this to after saving the PGN, just in case */
8221             /* engine died and we got here through time loss. In that */
8222             /* case we will get a fatal error writing the pipe, which */
8223             /* would otherwise lose us the PGN.                       */
8224             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8225             /* output during GameEnds should never be fatal anymore   */
8226             if (gameMode == MachinePlaysWhite ||
8227                 gameMode == MachinePlaysBlack ||
8228                 gameMode == TwoMachinesPlay ||
8229                 gameMode == IcsPlayingWhite ||
8230                 gameMode == IcsPlayingBlack ||
8231                 gameMode == BeginningOfGame) {
8232                 char buf[MSG_SIZ];
8233                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8234                         resultDetails);
8235                 if (first.pr != NoProc) {
8236                     SendToProgram(buf, &first);
8237                 }
8238                 if (second.pr != NoProc &&
8239                     gameMode == TwoMachinesPlay) {
8240                     SendToProgram(buf, &second);
8241                 }
8242             }
8243         }
8244
8245         if (appData.icsActive) {
8246             if (appData.quietPlay &&
8247                 (gameMode == IcsPlayingWhite ||
8248                  gameMode == IcsPlayingBlack)) {
8249                 SendToICS(ics_prefix);
8250                 SendToICS("set shout 1\n");
8251             }
8252             nextGameMode = IcsIdle;
8253             ics_user_moved = FALSE;
8254             /* clean up premove.  It's ugly when the game has ended and the
8255              * premove highlights are still on the board.
8256              */
8257             if (gotPremove) {
8258               gotPremove = FALSE;
8259               ClearPremoveHighlights();
8260               DrawPosition(FALSE, boards[currentMove]);
8261             }
8262             if (whosays == GE_ICS) {
8263                 switch (result) {
8264                 case WhiteWins:
8265                     if (gameMode == IcsPlayingWhite)
8266                         PlayIcsWinSound();
8267                     else if(gameMode == IcsPlayingBlack)
8268                         PlayIcsLossSound();
8269                     break;
8270                 case BlackWins:
8271                     if (gameMode == IcsPlayingBlack)
8272                         PlayIcsWinSound();
8273                     else if(gameMode == IcsPlayingWhite)
8274                         PlayIcsLossSound();
8275                     break;
8276                 case GameIsDrawn:
8277                     PlayIcsDrawSound();
8278                     break;
8279                 default:
8280                     PlayIcsUnfinishedSound();
8281                 }
8282             }
8283         } else if (gameMode == EditGame ||
8284                    gameMode == PlayFromGameFile || 
8285                    gameMode == AnalyzeMode || 
8286                    gameMode == AnalyzeFile) {
8287             nextGameMode = gameMode;
8288         } else {
8289             nextGameMode = EndOfGame;
8290         }
8291         pausing = FALSE;
8292         ModeHighlight();
8293     } else {
8294         nextGameMode = gameMode;
8295     }
8296
8297     if (appData.noChessProgram) {
8298         gameMode = nextGameMode;
8299         ModeHighlight();
8300         endingGame = 0; /* [HGM] crash */
8301         return;
8302     }
8303
8304     if (first.reuse) {
8305         /* Put first chess program into idle state */
8306         if (first.pr != NoProc &&
8307             (gameMode == MachinePlaysWhite ||
8308              gameMode == MachinePlaysBlack ||
8309              gameMode == TwoMachinesPlay ||
8310              gameMode == IcsPlayingWhite ||
8311              gameMode == IcsPlayingBlack ||
8312              gameMode == BeginningOfGame)) {
8313             SendToProgram("force\n", &first);
8314             if (first.usePing) {
8315               char buf[MSG_SIZ];
8316               sprintf(buf, "ping %d\n", ++first.lastPing);
8317               SendToProgram(buf, &first);
8318             }
8319         }
8320     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8321         /* Kill off first chess program */
8322         if (first.isr != NULL)
8323           RemoveInputSource(first.isr);
8324         first.isr = NULL;
8325     
8326         if (first.pr != NoProc) {
8327             ExitAnalyzeMode();
8328             DoSleep( appData.delayBeforeQuit );
8329             SendToProgram("quit\n", &first);
8330             DoSleep( appData.delayAfterQuit );
8331             DestroyChildProcess(first.pr, first.useSigterm);
8332         }
8333         first.pr = NoProc;
8334     }
8335     if (second.reuse) {
8336         /* Put second chess program into idle state */
8337         if (second.pr != NoProc &&
8338             gameMode == TwoMachinesPlay) {
8339             SendToProgram("force\n", &second);
8340             if (second.usePing) {
8341               char buf[MSG_SIZ];
8342               sprintf(buf, "ping %d\n", ++second.lastPing);
8343               SendToProgram(buf, &second);
8344             }
8345         }
8346     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8347         /* Kill off second chess program */
8348         if (second.isr != NULL)
8349           RemoveInputSource(second.isr);
8350         second.isr = NULL;
8351     
8352         if (second.pr != NoProc) {
8353             DoSleep( appData.delayBeforeQuit );
8354             SendToProgram("quit\n", &second);
8355             DoSleep( appData.delayAfterQuit );
8356             DestroyChildProcess(second.pr, second.useSigterm);
8357         }
8358         second.pr = NoProc;
8359     }
8360
8361     if (matchMode && gameMode == TwoMachinesPlay) {
8362         switch (result) {
8363         case WhiteWins:
8364           if (first.twoMachinesColor[0] == 'w') {
8365             first.matchWins++;
8366           } else {
8367             second.matchWins++;
8368           }
8369           break;
8370         case BlackWins:
8371           if (first.twoMachinesColor[0] == 'b') {
8372             first.matchWins++;
8373           } else {
8374             second.matchWins++;
8375           }
8376           break;
8377         default:
8378           break;
8379         }
8380         if (matchGame < appData.matchGames) {
8381             char *tmp;
8382             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8383                 tmp = first.twoMachinesColor;
8384                 first.twoMachinesColor = second.twoMachinesColor;
8385                 second.twoMachinesColor = tmp;
8386             }
8387             gameMode = nextGameMode;
8388             matchGame++;
8389             if(appData.matchPause>10000 || appData.matchPause<10)
8390                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8391             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8392             endingGame = 0; /* [HGM] crash */
8393             return;
8394         } else {
8395             char buf[MSG_SIZ];
8396             gameMode = nextGameMode;
8397             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8398                     first.tidy, second.tidy,
8399                     first.matchWins, second.matchWins,
8400                     appData.matchGames - (first.matchWins + second.matchWins));
8401             DisplayFatalError(buf, 0, 0);
8402         }
8403     }
8404     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8405         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8406       ExitAnalyzeMode();
8407     gameMode = nextGameMode;
8408     ModeHighlight();
8409     endingGame = 0;  /* [HGM] crash */
8410 }
8411
8412 /* Assumes program was just initialized (initString sent).
8413    Leaves program in force mode. */
8414 void
8415 FeedMovesToProgram(cps, upto) 
8416      ChessProgramState *cps;
8417      int upto;
8418 {
8419     int i;
8420     
8421     if (appData.debugMode)
8422       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8423               startedFromSetupPosition ? "position and " : "",
8424               backwardMostMove, upto, cps->which);
8425     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8426         // [HGM] variantswitch: make engine aware of new variant
8427         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8428                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8429         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8430         SendToProgram(buf, cps);
8431         currentlyInitializedVariant = gameInfo.variant;
8432     }
8433     SendToProgram("force\n", cps);
8434     if (startedFromSetupPosition) {
8435         SendBoard(cps, backwardMostMove);
8436     if (appData.debugMode) {
8437         fprintf(debugFP, "feedMoves\n");
8438     }
8439     }
8440     for (i = backwardMostMove; i < upto; i++) {
8441         SendMoveToProgram(i, cps);
8442     }
8443 }
8444
8445
8446 void
8447 ResurrectChessProgram()
8448 {
8449      /* The chess program may have exited.
8450         If so, restart it and feed it all the moves made so far. */
8451
8452     if (appData.noChessProgram || first.pr != NoProc) return;
8453     
8454     StartChessProgram(&first);
8455     InitChessProgram(&first, FALSE);
8456     FeedMovesToProgram(&first, currentMove);
8457
8458     if (!first.sendTime) {
8459         /* can't tell gnuchess what its clock should read,
8460            so we bow to its notion. */
8461         ResetClocks();
8462         timeRemaining[0][currentMove] = whiteTimeRemaining;
8463         timeRemaining[1][currentMove] = blackTimeRemaining;
8464     }
8465
8466     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8467                 appData.icsEngineAnalyze) && first.analysisSupport) {
8468       SendToProgram("analyze\n", &first);
8469       first.analyzing = TRUE;
8470     }
8471 }
8472
8473 /*
8474  * Button procedures
8475  */
8476 void
8477 Reset(redraw, init)
8478      int redraw, init;
8479 {
8480     int i;
8481
8482     if (appData.debugMode) {
8483         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8484                 redraw, init, gameMode);
8485     }
8486     pausing = pauseExamInvalid = FALSE;
8487     startedFromSetupPosition = blackPlaysFirst = FALSE;
8488     firstMove = TRUE;
8489     whiteFlag = blackFlag = FALSE;
8490     userOfferedDraw = FALSE;
8491     hintRequested = bookRequested = FALSE;
8492     first.maybeThinking = FALSE;
8493     second.maybeThinking = FALSE;
8494     first.bookSuspend = FALSE; // [HGM] book
8495     second.bookSuspend = FALSE;
8496     thinkOutput[0] = NULLCHAR;
8497     lastHint[0] = NULLCHAR;
8498     ClearGameInfo(&gameInfo);
8499     gameInfo.variant = StringToVariant(appData.variant);
8500     ics_user_moved = ics_clock_paused = FALSE;
8501     ics_getting_history = H_FALSE;
8502     ics_gamenum = -1;
8503     white_holding[0] = black_holding[0] = NULLCHAR;
8504     ClearProgramStats();
8505     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8506     
8507     ResetFrontEnd();
8508     ClearHighlights();
8509     flipView = appData.flipView;
8510     ClearPremoveHighlights();
8511     gotPremove = FALSE;
8512     alarmSounded = FALSE;
8513
8514     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8515     if(appData.serverMovesName != NULL) {
8516         /* [HGM] prepare to make moves file for broadcasting */
8517         clock_t t = clock();
8518         if(serverMoves != NULL) fclose(serverMoves);
8519         serverMoves = fopen(appData.serverMovesName, "r");
8520         if(serverMoves != NULL) {
8521             fclose(serverMoves);
8522             /* delay 15 sec before overwriting, so all clients can see end */
8523             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8524         }
8525         serverMoves = fopen(appData.serverMovesName, "w");
8526     }
8527
8528     ExitAnalyzeMode();
8529     gameMode = BeginningOfGame;
8530     ModeHighlight();
8531     if(appData.icsActive) gameInfo.variant = VariantNormal;
8532     currentMove = forwardMostMove = backwardMostMove = 0;
8533     InitPosition(redraw);
8534     for (i = 0; i < MAX_MOVES; i++) {
8535         if (commentList[i] != NULL) {
8536             free(commentList[i]);
8537             commentList[i] = NULL;
8538         }
8539     }
8540     ResetClocks();
8541     timeRemaining[0][0] = whiteTimeRemaining;
8542     timeRemaining[1][0] = blackTimeRemaining;
8543     if (first.pr == NULL) {
8544         StartChessProgram(&first);
8545     }
8546     if (init) {
8547             InitChessProgram(&first, startedFromSetupPosition);
8548     }
8549     DisplayTitle("");
8550     DisplayMessage("", "");
8551     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8552     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8553 }
8554
8555 void
8556 AutoPlayGameLoop()
8557 {
8558     for (;;) {
8559         if (!AutoPlayOneMove())
8560           return;
8561         if (matchMode || appData.timeDelay == 0)
8562           continue;
8563         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8564           return;
8565         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8566         break;
8567     }
8568 }
8569
8570
8571 int
8572 AutoPlayOneMove()
8573 {
8574     int fromX, fromY, toX, toY;
8575
8576     if (appData.debugMode) {
8577       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8578     }
8579
8580     if (gameMode != PlayFromGameFile)
8581       return FALSE;
8582
8583     if (currentMove >= forwardMostMove) {
8584       gameMode = EditGame;
8585       ModeHighlight();
8586
8587       /* [AS] Clear current move marker at the end of a game */
8588       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8589
8590       return FALSE;
8591     }
8592     
8593     toX = moveList[currentMove][2] - AAA;
8594     toY = moveList[currentMove][3] - ONE;
8595
8596     if (moveList[currentMove][1] == '@') {
8597         if (appData.highlightLastMove) {
8598             SetHighlights(-1, -1, toX, toY);
8599         }
8600     } else {
8601         fromX = moveList[currentMove][0] - AAA;
8602         fromY = moveList[currentMove][1] - ONE;
8603
8604         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8605
8606         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8607
8608         if (appData.highlightLastMove) {
8609             SetHighlights(fromX, fromY, toX, toY);
8610         }
8611     }
8612     DisplayMove(currentMove);
8613     SendMoveToProgram(currentMove++, &first);
8614     DisplayBothClocks();
8615     DrawPosition(FALSE, boards[currentMove]);
8616     // [HGM] PV info: always display, routine tests if empty
8617     DisplayComment(currentMove - 1, commentList[currentMove]);
8618     return TRUE;
8619 }
8620
8621
8622 int
8623 LoadGameOneMove(readAhead)
8624      ChessMove readAhead;
8625 {
8626     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8627     char promoChar = NULLCHAR;
8628     ChessMove moveType;
8629     char move[MSG_SIZ];
8630     char *p, *q;
8631     
8632     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8633         gameMode != AnalyzeMode && gameMode != Training) {
8634         gameFileFP = NULL;
8635         return FALSE;
8636     }
8637     
8638     yyboardindex = forwardMostMove;
8639     if (readAhead != (ChessMove)0) {
8640       moveType = readAhead;
8641     } else {
8642       if (gameFileFP == NULL)
8643           return FALSE;
8644       moveType = (ChessMove) yylex();
8645     }
8646     
8647     done = FALSE;
8648     switch (moveType) {
8649       case Comment:
8650         if (appData.debugMode) 
8651           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8652         p = yy_text;
8653         if (*p == '{' || *p == '[' || *p == '(') {
8654             p[strlen(p) - 1] = NULLCHAR;
8655             p++;
8656         }
8657
8658         /* append the comment but don't display it */
8659         while (*p == '\n') p++;
8660         AppendComment(currentMove, p);
8661         return TRUE;
8662
8663       case WhiteCapturesEnPassant:
8664       case BlackCapturesEnPassant:
8665       case WhitePromotionChancellor:
8666       case BlackPromotionChancellor:
8667       case WhitePromotionArchbishop:
8668       case BlackPromotionArchbishop:
8669       case WhitePromotionCentaur:
8670       case BlackPromotionCentaur:
8671       case WhitePromotionQueen:
8672       case BlackPromotionQueen:
8673       case WhitePromotionRook:
8674       case BlackPromotionRook:
8675       case WhitePromotionBishop:
8676       case BlackPromotionBishop:
8677       case WhitePromotionKnight:
8678       case BlackPromotionKnight:
8679       case WhitePromotionKing:
8680       case BlackPromotionKing:
8681       case NormalMove:
8682       case WhiteKingSideCastle:
8683       case WhiteQueenSideCastle:
8684       case BlackKingSideCastle:
8685       case BlackQueenSideCastle:
8686       case WhiteKingSideCastleWild:
8687       case WhiteQueenSideCastleWild:
8688       case BlackKingSideCastleWild:
8689       case BlackQueenSideCastleWild:
8690       /* PUSH Fabien */
8691       case WhiteHSideCastleFR:
8692       case WhiteASideCastleFR:
8693       case BlackHSideCastleFR:
8694       case BlackASideCastleFR:
8695       /* POP Fabien */
8696         if (appData.debugMode)
8697           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8698         fromX = currentMoveString[0] - AAA;
8699         fromY = currentMoveString[1] - ONE;
8700         toX = currentMoveString[2] - AAA;
8701         toY = currentMoveString[3] - ONE;
8702         promoChar = currentMoveString[4];
8703         break;
8704
8705       case WhiteDrop:
8706       case BlackDrop:
8707         if (appData.debugMode)
8708           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8709         fromX = moveType == WhiteDrop ?
8710           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8711         (int) CharToPiece(ToLower(currentMoveString[0]));
8712         fromY = DROP_RANK;
8713         toX = currentMoveString[2] - AAA;
8714         toY = currentMoveString[3] - ONE;
8715         break;
8716
8717       case WhiteWins:
8718       case BlackWins:
8719       case GameIsDrawn:
8720       case GameUnfinished:
8721         if (appData.debugMode)
8722           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8723         p = strchr(yy_text, '{');
8724         if (p == NULL) p = strchr(yy_text, '(');
8725         if (p == NULL) {
8726             p = yy_text;
8727             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8728         } else {
8729             q = strchr(p, *p == '{' ? '}' : ')');
8730             if (q != NULL) *q = NULLCHAR;
8731             p++;
8732         }
8733         GameEnds(moveType, p, GE_FILE);
8734         done = TRUE;
8735         if (cmailMsgLoaded) {
8736             ClearHighlights();
8737             flipView = WhiteOnMove(currentMove);
8738             if (moveType == GameUnfinished) flipView = !flipView;
8739             if (appData.debugMode)
8740               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8741         }
8742         break;
8743
8744       case (ChessMove) 0:       /* end of file */
8745         if (appData.debugMode)
8746           fprintf(debugFP, "Parser hit end of file\n");
8747         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8748                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8749           case MT_NONE:
8750           case MT_CHECK:
8751             break;
8752           case MT_CHECKMATE:
8753           case MT_STAINMATE:
8754             if (WhiteOnMove(currentMove)) {
8755                 GameEnds(BlackWins, "Black mates", GE_FILE);
8756             } else {
8757                 GameEnds(WhiteWins, "White mates", GE_FILE);
8758             }
8759             break;
8760           case MT_STALEMATE:
8761             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8762             break;
8763         }
8764         done = TRUE;
8765         break;
8766
8767       case MoveNumberOne:
8768         if (lastLoadGameStart == GNUChessGame) {
8769             /* GNUChessGames have numbers, but they aren't move numbers */
8770             if (appData.debugMode)
8771               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8772                       yy_text, (int) moveType);
8773             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8774         }
8775         /* else fall thru */
8776
8777       case XBoardGame:
8778       case GNUChessGame:
8779       case PGNTag:
8780         /* Reached start of next game in file */
8781         if (appData.debugMode)
8782           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8783         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8784                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8785           case MT_NONE:
8786           case MT_CHECK:
8787             break;
8788           case MT_CHECKMATE:
8789           case MT_STAINMATE:
8790             if (WhiteOnMove(currentMove)) {
8791                 GameEnds(BlackWins, "Black mates", GE_FILE);
8792             } else {
8793                 GameEnds(WhiteWins, "White mates", GE_FILE);
8794             }
8795             break;
8796           case MT_STALEMATE:
8797             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8798             break;
8799         }
8800         done = TRUE;
8801         break;
8802
8803       case PositionDiagram:     /* should not happen; ignore */
8804       case ElapsedTime:         /* ignore */
8805       case NAG:                 /* ignore */
8806         if (appData.debugMode)
8807           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8808                   yy_text, (int) moveType);
8809         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8810
8811       case IllegalMove:
8812         if (appData.testLegality) {
8813             if (appData.debugMode)
8814               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8815             sprintf(move, _("Illegal move: %d.%s%s"),
8816                     (forwardMostMove / 2) + 1,
8817                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8818             DisplayError(move, 0);
8819             done = TRUE;
8820         } else {
8821             if (appData.debugMode)
8822               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8823                       yy_text, currentMoveString);
8824             fromX = currentMoveString[0] - AAA;
8825             fromY = currentMoveString[1] - ONE;
8826             toX = currentMoveString[2] - AAA;
8827             toY = currentMoveString[3] - ONE;
8828             promoChar = currentMoveString[4];
8829         }
8830         break;
8831
8832       case AmbiguousMove:
8833         if (appData.debugMode)
8834           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8835         sprintf(move, _("Ambiguous move: %d.%s%s"),
8836                 (forwardMostMove / 2) + 1,
8837                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8838         DisplayError(move, 0);
8839         done = TRUE;
8840         break;
8841
8842       default:
8843       case ImpossibleMove:
8844         if (appData.debugMode)
8845           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8846         sprintf(move, _("Illegal move: %d.%s%s"),
8847                 (forwardMostMove / 2) + 1,
8848                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8849         DisplayError(move, 0);
8850         done = TRUE;
8851         break;
8852     }
8853
8854     if (done) {
8855         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8856             DrawPosition(FALSE, boards[currentMove]);
8857             DisplayBothClocks();
8858             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8859               DisplayComment(currentMove - 1, commentList[currentMove]);
8860         }
8861         (void) StopLoadGameTimer();
8862         gameFileFP = NULL;
8863         cmailOldMove = forwardMostMove;
8864         return FALSE;
8865     } else {
8866         /* currentMoveString is set as a side-effect of yylex */
8867         strcat(currentMoveString, "\n");
8868         strcpy(moveList[forwardMostMove], currentMoveString);
8869         
8870         thinkOutput[0] = NULLCHAR;
8871         MakeMove(fromX, fromY, toX, toY, promoChar);
8872         currentMove = forwardMostMove;
8873         return TRUE;
8874     }
8875 }
8876
8877 /* Load the nth game from the given file */
8878 int
8879 LoadGameFromFile(filename, n, title, useList)
8880      char *filename;
8881      int n;
8882      char *title;
8883      /*Boolean*/ int useList;
8884 {
8885     FILE *f;
8886     char buf[MSG_SIZ];
8887
8888     if (strcmp(filename, "-") == 0) {
8889         f = stdin;
8890         title = "stdin";
8891     } else {
8892         f = fopen(filename, "rb");
8893         if (f == NULL) {
8894           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8895             DisplayError(buf, errno);
8896             return FALSE;
8897         }
8898     }
8899     if (fseek(f, 0, 0) == -1) {
8900         /* f is not seekable; probably a pipe */
8901         useList = FALSE;
8902     }
8903     if (useList && n == 0) {
8904         int error = GameListBuild(f);
8905         if (error) {
8906             DisplayError(_("Cannot build game list"), error);
8907         } else if (!ListEmpty(&gameList) &&
8908                    ((ListGame *) gameList.tailPred)->number > 1) {
8909             GameListPopUp(f, title);
8910             return TRUE;
8911         }
8912         GameListDestroy();
8913         n = 1;
8914     }
8915     if (n == 0) n = 1;
8916     return LoadGame(f, n, title, FALSE);
8917 }
8918
8919
8920 void
8921 MakeRegisteredMove()
8922 {
8923     int fromX, fromY, toX, toY;
8924     char promoChar;
8925     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8926         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8927           case CMAIL_MOVE:
8928           case CMAIL_DRAW:
8929             if (appData.debugMode)
8930               fprintf(debugFP, "Restoring %s for game %d\n",
8931                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8932     
8933             thinkOutput[0] = NULLCHAR;
8934             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8935             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8936             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8937             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8938             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8939             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8940             MakeMove(fromX, fromY, toX, toY, promoChar);
8941             ShowMove(fromX, fromY, toX, toY);
8942               
8943             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8944                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8945               case MT_NONE:
8946               case MT_CHECK:
8947                 break;
8948                 
8949               case MT_CHECKMATE:
8950               case MT_STAINMATE:
8951                 if (WhiteOnMove(currentMove)) {
8952                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8953                 } else {
8954                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8955                 }
8956                 break;
8957                 
8958               case MT_STALEMATE:
8959                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8960                 break;
8961             }
8962
8963             break;
8964             
8965           case CMAIL_RESIGN:
8966             if (WhiteOnMove(currentMove)) {
8967                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8968             } else {
8969                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8970             }
8971             break;
8972             
8973           case CMAIL_ACCEPT:
8974             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8975             break;
8976               
8977           default:
8978             break;
8979         }
8980     }
8981
8982     return;
8983 }
8984
8985 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8986 int
8987 CmailLoadGame(f, gameNumber, title, useList)
8988      FILE *f;
8989      int gameNumber;
8990      char *title;
8991      int useList;
8992 {
8993     int retVal;
8994
8995     if (gameNumber > nCmailGames) {
8996         DisplayError(_("No more games in this message"), 0);
8997         return FALSE;
8998     }
8999     if (f == lastLoadGameFP) {
9000         int offset = gameNumber - lastLoadGameNumber;
9001         if (offset == 0) {
9002             cmailMsg[0] = NULLCHAR;
9003             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9004                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9005                 nCmailMovesRegistered--;
9006             }
9007             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9008             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9009                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9010             }
9011         } else {
9012             if (! RegisterMove()) return FALSE;
9013         }
9014     }
9015
9016     retVal = LoadGame(f, gameNumber, title, useList);
9017
9018     /* Make move registered during previous look at this game, if any */
9019     MakeRegisteredMove();
9020
9021     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9022         commentList[currentMove]
9023           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9024         DisplayComment(currentMove - 1, commentList[currentMove]);
9025     }
9026
9027     return retVal;
9028 }
9029
9030 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9031 int
9032 ReloadGame(offset)
9033      int offset;
9034 {
9035     int gameNumber = lastLoadGameNumber + offset;
9036     if (lastLoadGameFP == NULL) {
9037         DisplayError(_("No game has been loaded yet"), 0);
9038         return FALSE;
9039     }
9040     if (gameNumber <= 0) {
9041         DisplayError(_("Can't back up any further"), 0);
9042         return FALSE;
9043     }
9044     if (cmailMsgLoaded) {
9045         return CmailLoadGame(lastLoadGameFP, gameNumber,
9046                              lastLoadGameTitle, lastLoadGameUseList);
9047     } else {
9048         return LoadGame(lastLoadGameFP, gameNumber,
9049                         lastLoadGameTitle, lastLoadGameUseList);
9050     }
9051 }
9052
9053
9054
9055 /* Load the nth game from open file f */
9056 int
9057 LoadGame(f, gameNumber, title, useList)
9058      FILE *f;
9059      int gameNumber;
9060      char *title;
9061      int useList;
9062 {
9063     ChessMove cm;
9064     char buf[MSG_SIZ];
9065     int gn = gameNumber;
9066     ListGame *lg = NULL;
9067     int numPGNTags = 0;
9068     int err;
9069     GameMode oldGameMode;
9070     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9071
9072     if (appData.debugMode) 
9073         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9074
9075     if (gameMode == Training )
9076         SetTrainingModeOff();
9077
9078     oldGameMode = gameMode;
9079     if (gameMode != BeginningOfGame) {
9080       Reset(FALSE, TRUE);
9081     }
9082
9083     gameFileFP = f;
9084     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9085         fclose(lastLoadGameFP);
9086     }
9087
9088     if (useList) {
9089         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9090         
9091         if (lg) {
9092             fseek(f, lg->offset, 0);
9093             GameListHighlight(gameNumber);
9094             gn = 1;
9095         }
9096         else {
9097             DisplayError(_("Game number out of range"), 0);
9098             return FALSE;
9099         }
9100     } else {
9101         GameListDestroy();
9102         if (fseek(f, 0, 0) == -1) {
9103             if (f == lastLoadGameFP ?
9104                 gameNumber == lastLoadGameNumber + 1 :
9105                 gameNumber == 1) {
9106                 gn = 1;
9107             } else {
9108                 DisplayError(_("Can't seek on game file"), 0);
9109                 return FALSE;
9110             }
9111         }
9112     }
9113     lastLoadGameFP = f;
9114     lastLoadGameNumber = gameNumber;
9115     strcpy(lastLoadGameTitle, title);
9116     lastLoadGameUseList = useList;
9117
9118     yynewfile(f);
9119
9120     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9121       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9122                 lg->gameInfo.black);
9123             DisplayTitle(buf);
9124     } else if (*title != NULLCHAR) {
9125         if (gameNumber > 1) {
9126             sprintf(buf, "%s %d", title, gameNumber);
9127             DisplayTitle(buf);
9128         } else {
9129             DisplayTitle(title);
9130         }
9131     }
9132
9133     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9134         gameMode = PlayFromGameFile;
9135         ModeHighlight();
9136     }
9137
9138     currentMove = forwardMostMove = backwardMostMove = 0;
9139     CopyBoard(boards[0], initialPosition);
9140     StopClocks();
9141
9142     /*
9143      * Skip the first gn-1 games in the file.
9144      * Also skip over anything that precedes an identifiable 
9145      * start of game marker, to avoid being confused by 
9146      * garbage at the start of the file.  Currently 
9147      * recognized start of game markers are the move number "1",
9148      * the pattern "gnuchess .* game", the pattern
9149      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9150      * A game that starts with one of the latter two patterns
9151      * will also have a move number 1, possibly
9152      * following a position diagram.
9153      * 5-4-02: Let's try being more lenient and allowing a game to
9154      * start with an unnumbered move.  Does that break anything?
9155      */
9156     cm = lastLoadGameStart = (ChessMove) 0;
9157     while (gn > 0) {
9158         yyboardindex = forwardMostMove;
9159         cm = (ChessMove) yylex();
9160         switch (cm) {
9161           case (ChessMove) 0:
9162             if (cmailMsgLoaded) {
9163                 nCmailGames = CMAIL_MAX_GAMES - gn;
9164             } else {
9165                 Reset(TRUE, TRUE);
9166                 DisplayError(_("Game not found in file"), 0);
9167             }
9168             return FALSE;
9169
9170           case GNUChessGame:
9171           case XBoardGame:
9172             gn--;
9173             lastLoadGameStart = cm;
9174             break;
9175             
9176           case MoveNumberOne:
9177             switch (lastLoadGameStart) {
9178               case GNUChessGame:
9179               case XBoardGame:
9180               case PGNTag:
9181                 break;
9182               case MoveNumberOne:
9183               case (ChessMove) 0:
9184                 gn--;           /* count this game */
9185                 lastLoadGameStart = cm;
9186                 break;
9187               default:
9188                 /* impossible */
9189                 break;
9190             }
9191             break;
9192
9193           case PGNTag:
9194             switch (lastLoadGameStart) {
9195               case GNUChessGame:
9196               case PGNTag:
9197               case MoveNumberOne:
9198               case (ChessMove) 0:
9199                 gn--;           /* count this game */
9200                 lastLoadGameStart = cm;
9201                 break;
9202               case XBoardGame:
9203                 lastLoadGameStart = cm; /* game counted already */
9204                 break;
9205               default:
9206                 /* impossible */
9207                 break;
9208             }
9209             if (gn > 0) {
9210                 do {
9211                     yyboardindex = forwardMostMove;
9212                     cm = (ChessMove) yylex();
9213                 } while (cm == PGNTag || cm == Comment);
9214             }
9215             break;
9216
9217           case WhiteWins:
9218           case BlackWins:
9219           case GameIsDrawn:
9220             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9221                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9222                     != CMAIL_OLD_RESULT) {
9223                     nCmailResults ++ ;
9224                     cmailResult[  CMAIL_MAX_GAMES
9225                                 - gn - 1] = CMAIL_OLD_RESULT;
9226                 }
9227             }
9228             break;
9229
9230           case NormalMove:
9231             /* Only a NormalMove can be at the start of a game
9232              * without a position diagram. */
9233             if (lastLoadGameStart == (ChessMove) 0) {
9234               gn--;
9235               lastLoadGameStart = MoveNumberOne;
9236             }
9237             break;
9238
9239           default:
9240             break;
9241         }
9242     }
9243     
9244     if (appData.debugMode)
9245       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9246
9247     if (cm == XBoardGame) {
9248         /* Skip any header junk before position diagram and/or move 1 */
9249         for (;;) {
9250             yyboardindex = forwardMostMove;
9251             cm = (ChessMove) yylex();
9252
9253             if (cm == (ChessMove) 0 ||
9254                 cm == GNUChessGame || cm == XBoardGame) {
9255                 /* Empty game; pretend end-of-file and handle later */
9256                 cm = (ChessMove) 0;
9257                 break;
9258             }
9259
9260             if (cm == MoveNumberOne || cm == PositionDiagram ||
9261                 cm == PGNTag || cm == Comment)
9262               break;
9263         }
9264     } else if (cm == GNUChessGame) {
9265         if (gameInfo.event != NULL) {
9266             free(gameInfo.event);
9267         }
9268         gameInfo.event = StrSave(yy_text);
9269     }   
9270
9271     startedFromSetupPosition = FALSE;
9272     while (cm == PGNTag) {
9273         if (appData.debugMode) 
9274           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9275         err = ParsePGNTag(yy_text, &gameInfo);
9276         if (!err) numPGNTags++;
9277
9278         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9279         if(gameInfo.variant != oldVariant) {
9280             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9281             InitPosition(TRUE);
9282             oldVariant = gameInfo.variant;
9283             if (appData.debugMode) 
9284               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9285         }
9286
9287
9288         if (gameInfo.fen != NULL) {
9289           Board initial_position;
9290           startedFromSetupPosition = TRUE;
9291           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9292             Reset(TRUE, TRUE);
9293             DisplayError(_("Bad FEN position in file"), 0);
9294             return FALSE;
9295           }
9296           CopyBoard(boards[0], initial_position);
9297           if (blackPlaysFirst) {
9298             currentMove = forwardMostMove = backwardMostMove = 1;
9299             CopyBoard(boards[1], initial_position);
9300             strcpy(moveList[0], "");
9301             strcpy(parseList[0], "");
9302             timeRemaining[0][1] = whiteTimeRemaining;
9303             timeRemaining[1][1] = blackTimeRemaining;
9304             if (commentList[0] != NULL) {
9305               commentList[1] = commentList[0];
9306               commentList[0] = NULL;
9307             }
9308           } else {
9309             currentMove = forwardMostMove = backwardMostMove = 0;
9310           }
9311           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9312           {   int i;
9313               initialRulePlies = FENrulePlies;
9314               epStatus[forwardMostMove] = FENepStatus;
9315               for( i=0; i< nrCastlingRights; i++ )
9316                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9317           }
9318           yyboardindex = forwardMostMove;
9319           free(gameInfo.fen);
9320           gameInfo.fen = NULL;
9321         }
9322
9323         yyboardindex = forwardMostMove;
9324         cm = (ChessMove) yylex();
9325
9326         /* Handle comments interspersed among the tags */
9327         while (cm == Comment) {
9328             char *p;
9329             if (appData.debugMode) 
9330               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9331             p = yy_text;
9332             if (*p == '{' || *p == '[' || *p == '(') {
9333                 p[strlen(p) - 1] = NULLCHAR;
9334                 p++;
9335             }
9336             while (*p == '\n') p++;
9337             AppendComment(currentMove, p);
9338             yyboardindex = forwardMostMove;
9339             cm = (ChessMove) yylex();
9340         }
9341     }
9342
9343     /* don't rely on existence of Event tag since if game was
9344      * pasted from clipboard the Event tag may not exist
9345      */
9346     if (numPGNTags > 0){
9347         char *tags;
9348         if (gameInfo.variant == VariantNormal) {
9349           gameInfo.variant = StringToVariant(gameInfo.event);
9350         }
9351         if (!matchMode) {
9352           if( appData.autoDisplayTags ) {
9353             tags = PGNTags(&gameInfo);
9354             TagsPopUp(tags, CmailMsg());
9355             free(tags);
9356           }
9357         }
9358     } else {
9359         /* Make something up, but don't display it now */
9360         SetGameInfo();
9361         TagsPopDown();
9362     }
9363
9364     if (cm == PositionDiagram) {
9365         int i, j;
9366         char *p;
9367         Board initial_position;
9368
9369         if (appData.debugMode)
9370           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9371
9372         if (!startedFromSetupPosition) {
9373             p = yy_text;
9374             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9375               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9376                 switch (*p) {
9377                   case '[':
9378                   case '-':
9379                   case ' ':
9380                   case '\t':
9381                   case '\n':
9382                   case '\r':
9383                     break;
9384                   default:
9385                     initial_position[i][j++] = CharToPiece(*p);
9386                     break;
9387                 }
9388             while (*p == ' ' || *p == '\t' ||
9389                    *p == '\n' || *p == '\r') p++;
9390         
9391             if (strncmp(p, "black", strlen("black"))==0)
9392               blackPlaysFirst = TRUE;
9393             else
9394               blackPlaysFirst = FALSE;
9395             startedFromSetupPosition = TRUE;
9396         
9397             CopyBoard(boards[0], initial_position);
9398             if (blackPlaysFirst) {
9399                 currentMove = forwardMostMove = backwardMostMove = 1;
9400                 CopyBoard(boards[1], initial_position);
9401                 strcpy(moveList[0], "");
9402                 strcpy(parseList[0], "");
9403                 timeRemaining[0][1] = whiteTimeRemaining;
9404                 timeRemaining[1][1] = blackTimeRemaining;
9405                 if (commentList[0] != NULL) {
9406                     commentList[1] = commentList[0];
9407                     commentList[0] = NULL;
9408                 }
9409             } else {
9410                 currentMove = forwardMostMove = backwardMostMove = 0;
9411             }
9412         }
9413         yyboardindex = forwardMostMove;
9414         cm = (ChessMove) yylex();
9415     }
9416
9417     if (first.pr == NoProc) {
9418         StartChessProgram(&first);
9419     }
9420     InitChessProgram(&first, FALSE);
9421     SendToProgram("force\n", &first);
9422     if (startedFromSetupPosition) {
9423         SendBoard(&first, forwardMostMove);
9424     if (appData.debugMode) {
9425         fprintf(debugFP, "Load Game\n");
9426     }
9427         DisplayBothClocks();
9428     }      
9429
9430     /* [HGM] server: flag to write setup moves in broadcast file as one */
9431     loadFlag = appData.suppressLoadMoves;
9432
9433     while (cm == Comment) {
9434         char *p;
9435         if (appData.debugMode) 
9436           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9437         p = yy_text;
9438         if (*p == '{' || *p == '[' || *p == '(') {
9439             p[strlen(p) - 1] = NULLCHAR;
9440             p++;
9441         }
9442         while (*p == '\n') p++;
9443         AppendComment(currentMove, p);
9444         yyboardindex = forwardMostMove;
9445         cm = (ChessMove) yylex();
9446     }
9447
9448     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9449         cm == WhiteWins || cm == BlackWins ||
9450         cm == GameIsDrawn || cm == GameUnfinished) {
9451         DisplayMessage("", _("No moves in game"));
9452         if (cmailMsgLoaded) {
9453             if (appData.debugMode)
9454               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9455             ClearHighlights();
9456             flipView = FALSE;
9457         }
9458         DrawPosition(FALSE, boards[currentMove]);
9459         DisplayBothClocks();
9460         gameMode = EditGame;
9461         ModeHighlight();
9462         gameFileFP = NULL;
9463         cmailOldMove = 0;
9464         return TRUE;
9465     }
9466
9467     // [HGM] PV info: routine tests if comment empty
9468     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9469         DisplayComment(currentMove - 1, commentList[currentMove]);
9470     }
9471     if (!matchMode && appData.timeDelay != 0) 
9472       DrawPosition(FALSE, boards[currentMove]);
9473
9474     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9475       programStats.ok_to_send = 1;
9476     }
9477
9478     /* if the first token after the PGN tags is a move
9479      * and not move number 1, retrieve it from the parser 
9480      */
9481     if (cm != MoveNumberOne)
9482         LoadGameOneMove(cm);
9483
9484     /* load the remaining moves from the file */
9485     while (LoadGameOneMove((ChessMove)0)) {
9486       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9487       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9488     }
9489
9490     /* rewind to the start of the game */
9491     currentMove = backwardMostMove;
9492
9493     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9494
9495     if (oldGameMode == AnalyzeFile ||
9496         oldGameMode == AnalyzeMode) {
9497       AnalyzeFileEvent();
9498     }
9499
9500     if (matchMode || appData.timeDelay == 0) {
9501       ToEndEvent();
9502       gameMode = EditGame;
9503       ModeHighlight();
9504     } else if (appData.timeDelay > 0) {
9505       AutoPlayGameLoop();
9506     }
9507
9508     if (appData.debugMode) 
9509         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9510
9511     loadFlag = 0; /* [HGM] true game starts */
9512     return TRUE;
9513 }
9514
9515 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9516 int
9517 ReloadPosition(offset)
9518      int offset;
9519 {
9520     int positionNumber = lastLoadPositionNumber + offset;
9521     if (lastLoadPositionFP == NULL) {
9522         DisplayError(_("No position has been loaded yet"), 0);
9523         return FALSE;
9524     }
9525     if (positionNumber <= 0) {
9526         DisplayError(_("Can't back up any further"), 0);
9527         return FALSE;
9528     }
9529     return LoadPosition(lastLoadPositionFP, positionNumber,
9530                         lastLoadPositionTitle);
9531 }
9532
9533 /* Load the nth position from the given file */
9534 int
9535 LoadPositionFromFile(filename, n, title)
9536      char *filename;
9537      int n;
9538      char *title;
9539 {
9540     FILE *f;
9541     char buf[MSG_SIZ];
9542
9543     if (strcmp(filename, "-") == 0) {
9544         return LoadPosition(stdin, n, "stdin");
9545     } else {
9546         f = fopen(filename, "rb");
9547         if (f == NULL) {
9548             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9549             DisplayError(buf, errno);
9550             return FALSE;
9551         } else {
9552             return LoadPosition(f, n, title);
9553         }
9554     }
9555 }
9556
9557 /* Load the nth position from the given open file, and close it */
9558 int
9559 LoadPosition(f, positionNumber, title)
9560      FILE *f;
9561      int positionNumber;
9562      char *title;
9563 {
9564     char *p, line[MSG_SIZ];
9565     Board initial_position;
9566     int i, j, fenMode, pn;
9567     
9568     if (gameMode == Training )
9569         SetTrainingModeOff();
9570
9571     if (gameMode != BeginningOfGame) {
9572         Reset(FALSE, TRUE);
9573     }
9574     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9575         fclose(lastLoadPositionFP);
9576     }
9577     if (positionNumber == 0) positionNumber = 1;
9578     lastLoadPositionFP = f;
9579     lastLoadPositionNumber = positionNumber;
9580     strcpy(lastLoadPositionTitle, title);
9581     if (first.pr == NoProc) {
9582       StartChessProgram(&first);
9583       InitChessProgram(&first, FALSE);
9584     }    
9585     pn = positionNumber;
9586     if (positionNumber < 0) {
9587         /* Negative position number means to seek to that byte offset */
9588         if (fseek(f, -positionNumber, 0) == -1) {
9589             DisplayError(_("Can't seek on position file"), 0);
9590             return FALSE;
9591         };
9592         pn = 1;
9593     } else {
9594         if (fseek(f, 0, 0) == -1) {
9595             if (f == lastLoadPositionFP ?
9596                 positionNumber == lastLoadPositionNumber + 1 :
9597                 positionNumber == 1) {
9598                 pn = 1;
9599             } else {
9600                 DisplayError(_("Can't seek on position file"), 0);
9601                 return FALSE;
9602             }
9603         }
9604     }
9605     /* See if this file is FEN or old-style xboard */
9606     if (fgets(line, MSG_SIZ, f) == NULL) {
9607         DisplayError(_("Position not found in file"), 0);
9608         return FALSE;
9609     }
9610     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9611     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9612
9613     if (pn >= 2) {
9614         if (fenMode || line[0] == '#') pn--;
9615         while (pn > 0) {
9616             /* skip positions before number pn */
9617             if (fgets(line, MSG_SIZ, f) == NULL) {
9618                 Reset(TRUE, TRUE);
9619                 DisplayError(_("Position not found in file"), 0);
9620                 return FALSE;
9621             }
9622             if (fenMode || line[0] == '#') pn--;
9623         }
9624     }
9625
9626     if (fenMode) {
9627         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9628             DisplayError(_("Bad FEN position in file"), 0);
9629             return FALSE;
9630         }
9631     } else {
9632         (void) fgets(line, MSG_SIZ, f);
9633         (void) fgets(line, MSG_SIZ, f);
9634     
9635         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9636             (void) fgets(line, MSG_SIZ, f);
9637             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9638                 if (*p == ' ')
9639                   continue;
9640                 initial_position[i][j++] = CharToPiece(*p);
9641             }
9642         }
9643     
9644         blackPlaysFirst = FALSE;
9645         if (!feof(f)) {
9646             (void) fgets(line, MSG_SIZ, f);
9647             if (strncmp(line, "black", strlen("black"))==0)
9648               blackPlaysFirst = TRUE;
9649         }
9650     }
9651     startedFromSetupPosition = TRUE;
9652     
9653     SendToProgram("force\n", &first);
9654     CopyBoard(boards[0], initial_position);
9655     if (blackPlaysFirst) {
9656         currentMove = forwardMostMove = backwardMostMove = 1;
9657         strcpy(moveList[0], "");
9658         strcpy(parseList[0], "");
9659         CopyBoard(boards[1], initial_position);
9660         DisplayMessage("", _("Black to play"));
9661     } else {
9662         currentMove = forwardMostMove = backwardMostMove = 0;
9663         DisplayMessage("", _("White to play"));
9664     }
9665           /* [HGM] copy FEN attributes as well */
9666           {   int i;
9667               initialRulePlies = FENrulePlies;
9668               epStatus[forwardMostMove] = FENepStatus;
9669               for( i=0; i< nrCastlingRights; i++ )
9670                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9671           }
9672     SendBoard(&first, forwardMostMove);
9673     if (appData.debugMode) {
9674 int i, j;
9675   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9676   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9677         fprintf(debugFP, "Load Position\n");
9678     }
9679
9680     if (positionNumber > 1) {
9681         sprintf(line, "%s %d", title, positionNumber);
9682         DisplayTitle(line);
9683     } else {
9684         DisplayTitle(title);
9685     }
9686     gameMode = EditGame;
9687     ModeHighlight();
9688     ResetClocks();
9689     timeRemaining[0][1] = whiteTimeRemaining;
9690     timeRemaining[1][1] = blackTimeRemaining;
9691     DrawPosition(FALSE, boards[currentMove]);
9692    
9693     return TRUE;
9694 }
9695
9696
9697 void
9698 CopyPlayerNameIntoFileName(dest, src)
9699      char **dest, *src;
9700 {
9701     while (*src != NULLCHAR && *src != ',') {
9702         if (*src == ' ') {
9703             *(*dest)++ = '_';
9704             src++;
9705         } else {
9706             *(*dest)++ = *src++;
9707         }
9708     }
9709 }
9710
9711 char *DefaultFileName(ext)
9712      char *ext;
9713 {
9714     static char def[MSG_SIZ];
9715     char *p;
9716
9717     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9718         p = def;
9719         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9720         *p++ = '-';
9721         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9722         *p++ = '.';
9723         strcpy(p, ext);
9724     } else {
9725         def[0] = NULLCHAR;
9726     }
9727     return def;
9728 }
9729
9730 /* Save the current game to the given file */
9731 int
9732 SaveGameToFile(filename, append)
9733      char *filename;
9734      int append;
9735 {
9736     FILE *f;
9737     char buf[MSG_SIZ];
9738
9739     if (strcmp(filename, "-") == 0) {
9740         return SaveGame(stdout, 0, NULL);
9741     } else {
9742         f = fopen(filename, append ? "a" : "w");
9743         if (f == NULL) {
9744             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9745             DisplayError(buf, errno);
9746             return FALSE;
9747         } else {
9748             return SaveGame(f, 0, NULL);
9749         }
9750     }
9751 }
9752
9753 char *
9754 SavePart(str)
9755      char *str;
9756 {
9757     static char buf[MSG_SIZ];
9758     char *p;
9759     
9760     p = strchr(str, ' ');
9761     if (p == NULL) return str;
9762     strncpy(buf, str, p - str);
9763     buf[p - str] = NULLCHAR;
9764     return buf;
9765 }
9766
9767 #define PGN_MAX_LINE 75
9768
9769 #define PGN_SIDE_WHITE  0
9770 #define PGN_SIDE_BLACK  1
9771
9772 /* [AS] */
9773 static int FindFirstMoveOutOfBook( int side )
9774 {
9775     int result = -1;
9776
9777     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9778         int index = backwardMostMove;
9779         int has_book_hit = 0;
9780
9781         if( (index % 2) != side ) {
9782             index++;
9783         }
9784
9785         while( index < forwardMostMove ) {
9786             /* Check to see if engine is in book */
9787             int depth = pvInfoList[index].depth;
9788             int score = pvInfoList[index].score;
9789             int in_book = 0;
9790
9791             if( depth <= 2 ) {
9792                 in_book = 1;
9793             }
9794             else if( score == 0 && depth == 63 ) {
9795                 in_book = 1; /* Zappa */
9796             }
9797             else if( score == 2 && depth == 99 ) {
9798                 in_book = 1; /* Abrok */
9799             }
9800
9801             has_book_hit += in_book;
9802
9803             if( ! in_book ) {
9804                 result = index;
9805
9806                 break;
9807             }
9808
9809             index += 2;
9810         }
9811     }
9812
9813     return result;
9814 }
9815
9816 /* [AS] */
9817 void GetOutOfBookInfo( char * buf )
9818 {
9819     int oob[2];
9820     int i;
9821     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9822
9823     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9824     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9825
9826     *buf = '\0';
9827
9828     if( oob[0] >= 0 || oob[1] >= 0 ) {
9829         for( i=0; i<2; i++ ) {
9830             int idx = oob[i];
9831
9832             if( idx >= 0 ) {
9833                 if( i > 0 && oob[0] >= 0 ) {
9834                     strcat( buf, "   " );
9835                 }
9836
9837                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9838                 sprintf( buf+strlen(buf), "%s%.2f", 
9839                     pvInfoList[idx].score >= 0 ? "+" : "",
9840                     pvInfoList[idx].score / 100.0 );
9841             }
9842         }
9843     }
9844 }
9845
9846 /* Save game in PGN style and close the file */
9847 int
9848 SaveGamePGN(f)
9849      FILE *f;
9850 {
9851     int i, offset, linelen, newblock;
9852     time_t tm;
9853 //    char *movetext;
9854     char numtext[32];
9855     int movelen, numlen, blank;
9856     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9857
9858     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9859     
9860     tm = time((time_t *) NULL);
9861     
9862     PrintPGNTags(f, &gameInfo);
9863     
9864     if (backwardMostMove > 0 || startedFromSetupPosition) {
9865         char *fen = PositionToFEN(backwardMostMove, NULL);
9866         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9867         fprintf(f, "\n{--------------\n");
9868         PrintPosition(f, backwardMostMove);
9869         fprintf(f, "--------------}\n");
9870         free(fen);
9871     }
9872     else {
9873         /* [AS] Out of book annotation */
9874         if( appData.saveOutOfBookInfo ) {
9875             char buf[64];
9876
9877             GetOutOfBookInfo( buf );
9878
9879             if( buf[0] != '\0' ) {
9880                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9881             }
9882         }
9883
9884         fprintf(f, "\n");
9885     }
9886
9887     i = backwardMostMove;
9888     linelen = 0;
9889     newblock = TRUE;
9890
9891     while (i < forwardMostMove) {
9892         /* Print comments preceding this move */
9893         if (commentList[i] != NULL) {
9894             if (linelen > 0) fprintf(f, "\n");
9895             fprintf(f, "{\n%s}\n", commentList[i]);
9896             linelen = 0;
9897             newblock = TRUE;
9898         }
9899
9900         /* Format move number */
9901         if ((i % 2) == 0) {
9902             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9903         } else {
9904             if (newblock) {
9905                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9906             } else {
9907                 numtext[0] = NULLCHAR;
9908             }
9909         }
9910         numlen = strlen(numtext);
9911         newblock = FALSE;
9912
9913         /* Print move number */
9914         blank = linelen > 0 && numlen > 0;
9915         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9916             fprintf(f, "\n");
9917             linelen = 0;
9918             blank = 0;
9919         }
9920         if (blank) {
9921             fprintf(f, " ");
9922             linelen++;
9923         }
9924         fprintf(f, "%s", numtext);
9925         linelen += numlen;
9926
9927         /* Get move */
9928         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9929         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9930
9931         /* Print move */
9932         blank = linelen > 0 && movelen > 0;
9933         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9934             fprintf(f, "\n");
9935             linelen = 0;
9936             blank = 0;
9937         }
9938         if (blank) {
9939             fprintf(f, " ");
9940             linelen++;
9941         }
9942         fprintf(f, "%s", move_buffer);
9943         linelen += movelen;
9944
9945         /* [AS] Add PV info if present */
9946         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9947             /* [HGM] add time */
9948             char buf[MSG_SIZ]; int seconds = 0;
9949
9950             if(i >= backwardMostMove) {
9951                 if(WhiteOnMove(i))
9952                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9953                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9954                 else
9955                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9956                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9957             }
9958             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9959
9960             if( seconds <= 0) buf[0] = 0; else
9961             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9962                 seconds = (seconds + 4)/10; // round to full seconds
9963                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9964                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9965             }
9966
9967             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9968                 pvInfoList[i].score >= 0 ? "+" : "",
9969                 pvInfoList[i].score / 100.0,
9970                 pvInfoList[i].depth,
9971                 buf );
9972
9973             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9974
9975             /* Print score/depth */
9976             blank = linelen > 0 && movelen > 0;
9977             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9978                 fprintf(f, "\n");
9979                 linelen = 0;
9980                 blank = 0;
9981             }
9982             if (blank) {
9983                 fprintf(f, " ");
9984                 linelen++;
9985             }
9986             fprintf(f, "%s", move_buffer);
9987             linelen += movelen;
9988         }
9989
9990         i++;
9991     }
9992     
9993     /* Start a new line */
9994     if (linelen > 0) fprintf(f, "\n");
9995
9996     /* Print comments after last move */
9997     if (commentList[i] != NULL) {
9998         fprintf(f, "{\n%s}\n", commentList[i]);
9999     }
10000
10001     /* Print result */
10002     if (gameInfo.resultDetails != NULL &&
10003         gameInfo.resultDetails[0] != NULLCHAR) {
10004         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10005                 PGNResult(gameInfo.result));
10006     } else {
10007         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10008     }
10009
10010     fclose(f);
10011     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10012     return TRUE;
10013 }
10014
10015 /* Save game in old style and close the file */
10016 int
10017 SaveGameOldStyle(f)
10018      FILE *f;
10019 {
10020     int i, offset;
10021     time_t tm;
10022     
10023     tm = time((time_t *) NULL);
10024     
10025     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10026     PrintOpponents(f);
10027     
10028     if (backwardMostMove > 0 || startedFromSetupPosition) {
10029         fprintf(f, "\n[--------------\n");
10030         PrintPosition(f, backwardMostMove);
10031         fprintf(f, "--------------]\n");
10032     } else {
10033         fprintf(f, "\n");
10034     }
10035
10036     i = backwardMostMove;
10037     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10038
10039     while (i < forwardMostMove) {
10040         if (commentList[i] != NULL) {
10041             fprintf(f, "[%s]\n", commentList[i]);
10042         }
10043
10044         if ((i % 2) == 1) {
10045             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10046             i++;
10047         } else {
10048             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10049             i++;
10050             if (commentList[i] != NULL) {
10051                 fprintf(f, "\n");
10052                 continue;
10053             }
10054             if (i >= forwardMostMove) {
10055                 fprintf(f, "\n");
10056                 break;
10057             }
10058             fprintf(f, "%s\n", parseList[i]);
10059             i++;
10060         }
10061     }
10062     
10063     if (commentList[i] != NULL) {
10064         fprintf(f, "[%s]\n", commentList[i]);
10065     }
10066
10067     /* This isn't really the old style, but it's close enough */
10068     if (gameInfo.resultDetails != NULL &&
10069         gameInfo.resultDetails[0] != NULLCHAR) {
10070         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10071                 gameInfo.resultDetails);
10072     } else {
10073         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10074     }
10075
10076     fclose(f);
10077     return TRUE;
10078 }
10079
10080 /* Save the current game to open file f and close the file */
10081 int
10082 SaveGame(f, dummy, dummy2)
10083      FILE *f;
10084      int dummy;
10085      char *dummy2;
10086 {
10087     if (gameMode == EditPosition) EditPositionDone();
10088     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10089     if (appData.oldSaveStyle)
10090       return SaveGameOldStyle(f);
10091     else
10092       return SaveGamePGN(f);
10093 }
10094
10095 /* Save the current position to the given file */
10096 int
10097 SavePositionToFile(filename)
10098      char *filename;
10099 {
10100     FILE *f;
10101     char buf[MSG_SIZ];
10102
10103     if (strcmp(filename, "-") == 0) {
10104         return SavePosition(stdout, 0, NULL);
10105     } else {
10106         f = fopen(filename, "a");
10107         if (f == NULL) {
10108             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10109             DisplayError(buf, errno);
10110             return FALSE;
10111         } else {
10112             SavePosition(f, 0, NULL);
10113             return TRUE;
10114         }
10115     }
10116 }
10117
10118 /* Save the current position to the given open file and close the file */
10119 int
10120 SavePosition(f, dummy, dummy2)
10121      FILE *f;
10122      int dummy;
10123      char *dummy2;
10124 {
10125     time_t tm;
10126     char *fen;
10127     
10128     if (appData.oldSaveStyle) {
10129         tm = time((time_t *) NULL);
10130     
10131         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10132         PrintOpponents(f);
10133         fprintf(f, "[--------------\n");
10134         PrintPosition(f, currentMove);
10135         fprintf(f, "--------------]\n");
10136     } else {
10137         fen = PositionToFEN(currentMove, NULL);
10138         fprintf(f, "%s\n", fen);
10139         free(fen);
10140     }
10141     fclose(f);
10142     return TRUE;
10143 }
10144
10145 void
10146 ReloadCmailMsgEvent(unregister)
10147      int unregister;
10148 {
10149 #if !WIN32
10150     static char *inFilename = NULL;
10151     static char *outFilename;
10152     int i;
10153     struct stat inbuf, outbuf;
10154     int status;
10155     
10156     /* Any registered moves are unregistered if unregister is set, */
10157     /* i.e. invoked by the signal handler */
10158     if (unregister) {
10159         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10160             cmailMoveRegistered[i] = FALSE;
10161             if (cmailCommentList[i] != NULL) {
10162                 free(cmailCommentList[i]);
10163                 cmailCommentList[i] = NULL;
10164             }
10165         }
10166         nCmailMovesRegistered = 0;
10167     }
10168
10169     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10170         cmailResult[i] = CMAIL_NOT_RESULT;
10171     }
10172     nCmailResults = 0;
10173
10174     if (inFilename == NULL) {
10175         /* Because the filenames are static they only get malloced once  */
10176         /* and they never get freed                                      */
10177         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10178         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10179
10180         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10181         sprintf(outFilename, "%s.out", appData.cmailGameName);
10182     }
10183     
10184     status = stat(outFilename, &outbuf);
10185     if (status < 0) {
10186         cmailMailedMove = FALSE;
10187     } else {
10188         status = stat(inFilename, &inbuf);
10189         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10190     }
10191     
10192     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10193        counts the games, notes how each one terminated, etc.
10194        
10195        It would be nice to remove this kludge and instead gather all
10196        the information while building the game list.  (And to keep it
10197        in the game list nodes instead of having a bunch of fixed-size
10198        parallel arrays.)  Note this will require getting each game's
10199        termination from the PGN tags, as the game list builder does
10200        not process the game moves.  --mann
10201        */
10202     cmailMsgLoaded = TRUE;
10203     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10204     
10205     /* Load first game in the file or popup game menu */
10206     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10207
10208 #endif /* !WIN32 */
10209     return;
10210 }
10211
10212 int
10213 RegisterMove()
10214 {
10215     FILE *f;
10216     char string[MSG_SIZ];
10217
10218     if (   cmailMailedMove
10219         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10220         return TRUE;            /* Allow free viewing  */
10221     }
10222
10223     /* Unregister move to ensure that we don't leave RegisterMove        */
10224     /* with the move registered when the conditions for registering no   */
10225     /* longer hold                                                       */
10226     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10227         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10228         nCmailMovesRegistered --;
10229
10230         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10231           {
10232               free(cmailCommentList[lastLoadGameNumber - 1]);
10233               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10234           }
10235     }
10236
10237     if (cmailOldMove == -1) {
10238         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10239         return FALSE;
10240     }
10241
10242     if (currentMove > cmailOldMove + 1) {
10243         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10244         return FALSE;
10245     }
10246
10247     if (currentMove < cmailOldMove) {
10248         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10249         return FALSE;
10250     }
10251
10252     if (forwardMostMove > currentMove) {
10253         /* Silently truncate extra moves */
10254         TruncateGame();
10255     }
10256
10257     if (   (currentMove == cmailOldMove + 1)
10258         || (   (currentMove == cmailOldMove)
10259             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10260                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10261         if (gameInfo.result != GameUnfinished) {
10262             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10263         }
10264
10265         if (commentList[currentMove] != NULL) {
10266             cmailCommentList[lastLoadGameNumber - 1]
10267               = StrSave(commentList[currentMove]);
10268         }
10269         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10270
10271         if (appData.debugMode)
10272           fprintf(debugFP, "Saving %s for game %d\n",
10273                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10274
10275         sprintf(string,
10276                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10277         
10278         f = fopen(string, "w");
10279         if (appData.oldSaveStyle) {
10280             SaveGameOldStyle(f); /* also closes the file */
10281             
10282             sprintf(string, "%s.pos.out", appData.cmailGameName);
10283             f = fopen(string, "w");
10284             SavePosition(f, 0, NULL); /* also closes the file */
10285         } else {
10286             fprintf(f, "{--------------\n");
10287             PrintPosition(f, currentMove);
10288             fprintf(f, "--------------}\n\n");
10289             
10290             SaveGame(f, 0, NULL); /* also closes the file*/
10291         }
10292         
10293         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10294         nCmailMovesRegistered ++;
10295     } else if (nCmailGames == 1) {
10296         DisplayError(_("You have not made a move yet"), 0);
10297         return FALSE;
10298     }
10299
10300     return TRUE;
10301 }
10302
10303 void
10304 MailMoveEvent()
10305 {
10306 #if !WIN32
10307     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10308     FILE *commandOutput;
10309     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10310     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10311     int nBuffers;
10312     int i;
10313     int archived;
10314     char *arcDir;
10315
10316     if (! cmailMsgLoaded) {
10317         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10318         return;
10319     }
10320
10321     if (nCmailGames == nCmailResults) {
10322         DisplayError(_("No unfinished games"), 0);
10323         return;
10324     }
10325
10326 #if CMAIL_PROHIBIT_REMAIL
10327     if (cmailMailedMove) {
10328         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);
10329         DisplayError(msg, 0);
10330         return;
10331     }
10332 #endif
10333
10334     if (! (cmailMailedMove || RegisterMove())) return;
10335     
10336     if (   cmailMailedMove
10337         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10338         sprintf(string, partCommandString,
10339                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10340         commandOutput = popen(string, "r");
10341
10342         if (commandOutput == NULL) {
10343             DisplayError(_("Failed to invoke cmail"), 0);
10344         } else {
10345             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10346                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10347             }
10348             if (nBuffers > 1) {
10349                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10350                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10351                 nBytes = MSG_SIZ - 1;
10352             } else {
10353                 (void) memcpy(msg, buffer, nBytes);
10354             }
10355             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10356
10357             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10358                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10359
10360                 archived = TRUE;
10361                 for (i = 0; i < nCmailGames; i ++) {
10362                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10363                         archived = FALSE;
10364                     }
10365                 }
10366                 if (   archived
10367                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10368                         != NULL)) {
10369                     sprintf(buffer, "%s/%s.%s.archive",
10370                             arcDir,
10371                             appData.cmailGameName,
10372                             gameInfo.date);
10373                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10374                     cmailMsgLoaded = FALSE;
10375                 }
10376             }
10377
10378             DisplayInformation(msg);
10379             pclose(commandOutput);
10380         }
10381     } else {
10382         if ((*cmailMsg) != '\0') {
10383             DisplayInformation(cmailMsg);
10384         }
10385     }
10386
10387     return;
10388 #endif /* !WIN32 */
10389 }
10390
10391 char *
10392 CmailMsg()
10393 {
10394 #if WIN32
10395     return NULL;
10396 #else
10397     int  prependComma = 0;
10398     char number[5];
10399     char string[MSG_SIZ];       /* Space for game-list */
10400     int  i;
10401     
10402     if (!cmailMsgLoaded) return "";
10403
10404     if (cmailMailedMove) {
10405         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10406     } else {
10407         /* Create a list of games left */
10408         sprintf(string, "[");
10409         for (i = 0; i < nCmailGames; i ++) {
10410             if (! (   cmailMoveRegistered[i]
10411                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10412                 if (prependComma) {
10413                     sprintf(number, ",%d", i + 1);
10414                 } else {
10415                     sprintf(number, "%d", i + 1);
10416                     prependComma = 1;
10417                 }
10418                 
10419                 strcat(string, number);
10420             }
10421         }
10422         strcat(string, "]");
10423
10424         if (nCmailMovesRegistered + nCmailResults == 0) {
10425             switch (nCmailGames) {
10426               case 1:
10427                 sprintf(cmailMsg,
10428                         _("Still need to make move for game\n"));
10429                 break;
10430                 
10431               case 2:
10432                 sprintf(cmailMsg,
10433                         _("Still need to make moves for both games\n"));
10434                 break;
10435                 
10436               default:
10437                 sprintf(cmailMsg,
10438                         _("Still need to make moves for all %d games\n"),
10439                         nCmailGames);
10440                 break;
10441             }
10442         } else {
10443             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10444               case 1:
10445                 sprintf(cmailMsg,
10446                         _("Still need to make a move for game %s\n"),
10447                         string);
10448                 break;
10449                 
10450               case 0:
10451                 if (nCmailResults == nCmailGames) {
10452                     sprintf(cmailMsg, _("No unfinished games\n"));
10453                 } else {
10454                     sprintf(cmailMsg, _("Ready to send mail\n"));
10455                 }
10456                 break;
10457                 
10458               default:
10459                 sprintf(cmailMsg,
10460                         _("Still need to make moves for games %s\n"),
10461                         string);
10462             }
10463         }
10464     }
10465     return cmailMsg;
10466 #endif /* WIN32 */
10467 }
10468
10469 void
10470 ResetGameEvent()
10471 {
10472     if (gameMode == Training)
10473       SetTrainingModeOff();
10474
10475     Reset(TRUE, TRUE);
10476     cmailMsgLoaded = FALSE;
10477     if (appData.icsActive) {
10478       SendToICS(ics_prefix);
10479       SendToICS("refresh\n");
10480     }
10481 }
10482
10483 void
10484 ExitEvent(status)
10485      int status;
10486 {
10487     exiting++;
10488     if (exiting > 2) {
10489       /* Give up on clean exit */
10490       exit(status);
10491     }
10492     if (exiting > 1) {
10493       /* Keep trying for clean exit */
10494       return;
10495     }
10496
10497     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10498
10499     if (telnetISR != NULL) {
10500       RemoveInputSource(telnetISR);
10501     }
10502     if (icsPR != NoProc) {
10503       DestroyChildProcess(icsPR, TRUE);
10504     }
10505
10506     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10507     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10508
10509     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10510     /* make sure this other one finishes before killing it!                  */
10511     if(endingGame) { int count = 0;
10512         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10513         while(endingGame && count++ < 10) DoSleep(1);
10514         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10515     }
10516
10517     /* Kill off chess programs */
10518     if (first.pr != NoProc) {
10519         ExitAnalyzeMode();
10520         
10521         DoSleep( appData.delayBeforeQuit );
10522         SendToProgram("quit\n", &first);
10523         DoSleep( appData.delayAfterQuit );
10524         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10525     }
10526     if (second.pr != NoProc) {
10527         DoSleep( appData.delayBeforeQuit );
10528         SendToProgram("quit\n", &second);
10529         DoSleep( appData.delayAfterQuit );
10530         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10531     }
10532     if (first.isr != NULL) {
10533         RemoveInputSource(first.isr);
10534     }
10535     if (second.isr != NULL) {
10536         RemoveInputSource(second.isr);
10537     }
10538
10539     ShutDownFrontEnd();
10540     exit(status);
10541 }
10542
10543 void
10544 PauseEvent()
10545 {
10546     if (appData.debugMode)
10547         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10548     if (pausing) {
10549         pausing = FALSE;
10550         ModeHighlight();
10551         if (gameMode == MachinePlaysWhite ||
10552             gameMode == MachinePlaysBlack) {
10553             StartClocks();
10554         } else {
10555             DisplayBothClocks();
10556         }
10557         if (gameMode == PlayFromGameFile) {
10558             if (appData.timeDelay >= 0) 
10559                 AutoPlayGameLoop();
10560         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10561             Reset(FALSE, TRUE);
10562             SendToICS(ics_prefix);
10563             SendToICS("refresh\n");
10564         } else if (currentMove < forwardMostMove) {
10565             ForwardInner(forwardMostMove);
10566         }
10567         pauseExamInvalid = FALSE;
10568     } else {
10569         switch (gameMode) {
10570           default:
10571             return;
10572           case IcsExamining:
10573             pauseExamForwardMostMove = forwardMostMove;
10574             pauseExamInvalid = FALSE;
10575             /* fall through */
10576           case IcsObserving:
10577           case IcsPlayingWhite:
10578           case IcsPlayingBlack:
10579             pausing = TRUE;
10580             ModeHighlight();
10581             return;
10582           case PlayFromGameFile:
10583             (void) StopLoadGameTimer();
10584             pausing = TRUE;
10585             ModeHighlight();
10586             break;
10587           case BeginningOfGame:
10588             if (appData.icsActive) return;
10589             /* else fall through */
10590           case MachinePlaysWhite:
10591           case MachinePlaysBlack:
10592           case TwoMachinesPlay:
10593             if (forwardMostMove == 0)
10594               return;           /* don't pause if no one has moved */
10595             if ((gameMode == MachinePlaysWhite &&
10596                  !WhiteOnMove(forwardMostMove)) ||
10597                 (gameMode == MachinePlaysBlack &&
10598                  WhiteOnMove(forwardMostMove))) {
10599                 StopClocks();
10600             }
10601             pausing = TRUE;
10602             ModeHighlight();
10603             break;
10604         }
10605     }
10606 }
10607
10608 void
10609 EditCommentEvent()
10610 {
10611     char title[MSG_SIZ];
10612
10613     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10614         strcpy(title, _("Edit comment"));
10615     } else {
10616         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10617                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10618                 parseList[currentMove - 1]);
10619     }
10620
10621     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10622 }
10623
10624
10625 void
10626 EditTagsEvent()
10627 {
10628     char *tags = PGNTags(&gameInfo);
10629     EditTagsPopUp(tags);
10630     free(tags);
10631 }
10632
10633 void
10634 AnalyzeModeEvent()
10635 {
10636     if (appData.noChessProgram || gameMode == AnalyzeMode)
10637       return;
10638
10639     if (gameMode != AnalyzeFile) {
10640         if (!appData.icsEngineAnalyze) {
10641                EditGameEvent();
10642                if (gameMode != EditGame) return;
10643         }
10644         ResurrectChessProgram();
10645         SendToProgram("analyze\n", &first);
10646         first.analyzing = TRUE;
10647         /*first.maybeThinking = TRUE;*/
10648         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10649         EngineOutputPopUp();
10650     }
10651     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10652     pausing = FALSE;
10653     ModeHighlight();
10654     SetGameInfo();
10655
10656     StartAnalysisClock();
10657     GetTimeMark(&lastNodeCountTime);
10658     lastNodeCount = 0;
10659 }
10660
10661 void
10662 AnalyzeFileEvent()
10663 {
10664     if (appData.noChessProgram || gameMode == AnalyzeFile)
10665       return;
10666
10667     if (gameMode != AnalyzeMode) {
10668         EditGameEvent();
10669         if (gameMode != EditGame) return;
10670         ResurrectChessProgram();
10671         SendToProgram("analyze\n", &first);
10672         first.analyzing = TRUE;
10673         /*first.maybeThinking = TRUE;*/
10674         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10675         EngineOutputPopUp();
10676     }
10677     gameMode = AnalyzeFile;
10678     pausing = FALSE;
10679     ModeHighlight();
10680     SetGameInfo();
10681
10682     StartAnalysisClock();
10683     GetTimeMark(&lastNodeCountTime);
10684     lastNodeCount = 0;
10685 }
10686
10687 void
10688 MachineWhiteEvent()
10689 {
10690     char buf[MSG_SIZ];
10691     char *bookHit = NULL;
10692
10693     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10694       return;
10695
10696
10697     if (gameMode == PlayFromGameFile || 
10698         gameMode == TwoMachinesPlay  || 
10699         gameMode == Training         || 
10700         gameMode == AnalyzeMode      || 
10701         gameMode == EndOfGame)
10702         EditGameEvent();
10703
10704     if (gameMode == EditPosition) 
10705         EditPositionDone();
10706
10707     if (!WhiteOnMove(currentMove)) {
10708         DisplayError(_("It is not White's turn"), 0);
10709         return;
10710     }
10711   
10712     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10713       ExitAnalyzeMode();
10714
10715     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10716         gameMode == AnalyzeFile)
10717         TruncateGame();
10718
10719     ResurrectChessProgram();    /* in case it isn't running */
10720     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10721         gameMode = MachinePlaysWhite;
10722         ResetClocks();
10723     } else
10724     gameMode = MachinePlaysWhite;
10725     pausing = FALSE;
10726     ModeHighlight();
10727     SetGameInfo();
10728     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10729     DisplayTitle(buf);
10730     if (first.sendName) {
10731       sprintf(buf, "name %s\n", gameInfo.black);
10732       SendToProgram(buf, &first);
10733     }
10734     if (first.sendTime) {
10735       if (first.useColors) {
10736         SendToProgram("black\n", &first); /*gnu kludge*/
10737       }
10738       SendTimeRemaining(&first, TRUE);
10739     }
10740     if (first.useColors) {
10741       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10742     }
10743     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10744     SetMachineThinkingEnables();
10745     first.maybeThinking = TRUE;
10746     StartClocks();
10747     firstMove = FALSE;
10748
10749     if (appData.autoFlipView && !flipView) {
10750       flipView = !flipView;
10751       DrawPosition(FALSE, NULL);
10752       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10753     }
10754
10755     if(bookHit) { // [HGM] book: simulate book reply
10756         static char bookMove[MSG_SIZ]; // a bit generous?
10757
10758         programStats.nodes = programStats.depth = programStats.time = 
10759         programStats.score = programStats.got_only_move = 0;
10760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10761
10762         strcpy(bookMove, "move ");
10763         strcat(bookMove, bookHit);
10764         HandleMachineMove(bookMove, &first);
10765     }
10766 }
10767
10768 void
10769 MachineBlackEvent()
10770 {
10771     char buf[MSG_SIZ];
10772    char *bookHit = NULL;
10773
10774     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10775         return;
10776
10777
10778     if (gameMode == PlayFromGameFile || 
10779         gameMode == TwoMachinesPlay  || 
10780         gameMode == Training         || 
10781         gameMode == AnalyzeMode      || 
10782         gameMode == EndOfGame)
10783         EditGameEvent();
10784
10785     if (gameMode == EditPosition) 
10786         EditPositionDone();
10787
10788     if (WhiteOnMove(currentMove)) {
10789         DisplayError(_("It is not Black's turn"), 0);
10790         return;
10791     }
10792     
10793     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10794       ExitAnalyzeMode();
10795
10796     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10797         gameMode == AnalyzeFile)
10798         TruncateGame();
10799
10800     ResurrectChessProgram();    /* in case it isn't running */
10801     gameMode = MachinePlaysBlack;
10802     pausing = FALSE;
10803     ModeHighlight();
10804     SetGameInfo();
10805     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10806     DisplayTitle(buf);
10807     if (first.sendName) {
10808       sprintf(buf, "name %s\n", gameInfo.white);
10809       SendToProgram(buf, &first);
10810     }
10811     if (first.sendTime) {
10812       if (first.useColors) {
10813         SendToProgram("white\n", &first); /*gnu kludge*/
10814       }
10815       SendTimeRemaining(&first, FALSE);
10816     }
10817     if (first.useColors) {
10818       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10819     }
10820     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10821     SetMachineThinkingEnables();
10822     first.maybeThinking = TRUE;
10823     StartClocks();
10824
10825     if (appData.autoFlipView && flipView) {
10826       flipView = !flipView;
10827       DrawPosition(FALSE, NULL);
10828       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10829     }
10830     if(bookHit) { // [HGM] book: simulate book reply
10831         static char bookMove[MSG_SIZ]; // a bit generous?
10832
10833         programStats.nodes = programStats.depth = programStats.time = 
10834         programStats.score = programStats.got_only_move = 0;
10835         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10836
10837         strcpy(bookMove, "move ");
10838         strcat(bookMove, bookHit);
10839         HandleMachineMove(bookMove, &first);
10840     }
10841 }
10842
10843
10844 void
10845 DisplayTwoMachinesTitle()
10846 {
10847     char buf[MSG_SIZ];
10848     if (appData.matchGames > 0) {
10849         if (first.twoMachinesColor[0] == 'w') {
10850             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10851                     gameInfo.white, gameInfo.black,
10852                     first.matchWins, second.matchWins,
10853                     matchGame - 1 - (first.matchWins + second.matchWins));
10854         } else {
10855             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10856                     gameInfo.white, gameInfo.black,
10857                     second.matchWins, first.matchWins,
10858                     matchGame - 1 - (first.matchWins + second.matchWins));
10859         }
10860     } else {
10861         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10862     }
10863     DisplayTitle(buf);
10864 }
10865
10866 void
10867 TwoMachinesEvent P((void))
10868 {
10869     int i;
10870     char buf[MSG_SIZ];
10871     ChessProgramState *onmove;
10872     char *bookHit = NULL;
10873     
10874     if (appData.noChessProgram) return;
10875
10876     switch (gameMode) {
10877       case TwoMachinesPlay:
10878         return;
10879       case MachinePlaysWhite:
10880       case MachinePlaysBlack:
10881         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10882             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10883             return;
10884         }
10885         /* fall through */
10886       case BeginningOfGame:
10887       case PlayFromGameFile:
10888       case EndOfGame:
10889         EditGameEvent();
10890         if (gameMode != EditGame) return;
10891         break;
10892       case EditPosition:
10893         EditPositionDone();
10894         break;
10895       case AnalyzeMode:
10896       case AnalyzeFile:
10897         ExitAnalyzeMode();
10898         break;
10899       case EditGame:
10900       default:
10901         break;
10902     }
10903
10904     forwardMostMove = currentMove;
10905     ResurrectChessProgram();    /* in case first program isn't running */
10906
10907     if (second.pr == NULL) {
10908         StartChessProgram(&second);
10909         if (second.protocolVersion == 1) {
10910           TwoMachinesEventIfReady();
10911         } else {
10912           /* kludge: allow timeout for initial "feature" command */
10913           FreezeUI();
10914           DisplayMessage("", _("Starting second chess program"));
10915           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10916         }
10917         return;
10918     }
10919     DisplayMessage("", "");
10920     InitChessProgram(&second, FALSE);
10921     SendToProgram("force\n", &second);
10922     if (startedFromSetupPosition) {
10923         SendBoard(&second, backwardMostMove);
10924     if (appData.debugMode) {
10925         fprintf(debugFP, "Two Machines\n");
10926     }
10927     }
10928     for (i = backwardMostMove; i < forwardMostMove; i++) {
10929         SendMoveToProgram(i, &second);
10930     }
10931
10932     gameMode = TwoMachinesPlay;
10933     pausing = FALSE;
10934     ModeHighlight();
10935     SetGameInfo();
10936     DisplayTwoMachinesTitle();
10937     firstMove = TRUE;
10938     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10939         onmove = &first;
10940     } else {
10941         onmove = &second;
10942     }
10943
10944     SendToProgram(first.computerString, &first);
10945     if (first.sendName) {
10946       sprintf(buf, "name %s\n", second.tidy);
10947       SendToProgram(buf, &first);
10948     }
10949     SendToProgram(second.computerString, &second);
10950     if (second.sendName) {
10951       sprintf(buf, "name %s\n", first.tidy);
10952       SendToProgram(buf, &second);
10953     }
10954
10955     ResetClocks();
10956     if (!first.sendTime || !second.sendTime) {
10957         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10958         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10959     }
10960     if (onmove->sendTime) {
10961       if (onmove->useColors) {
10962         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10963       }
10964       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10965     }
10966     if (onmove->useColors) {
10967       SendToProgram(onmove->twoMachinesColor, onmove);
10968     }
10969     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10970 //    SendToProgram("go\n", onmove);
10971     onmove->maybeThinking = TRUE;
10972     SetMachineThinkingEnables();
10973
10974     StartClocks();
10975
10976     if(bookHit) { // [HGM] book: simulate book reply
10977         static char bookMove[MSG_SIZ]; // a bit generous?
10978
10979         programStats.nodes = programStats.depth = programStats.time = 
10980         programStats.score = programStats.got_only_move = 0;
10981         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10982
10983         strcpy(bookMove, "move ");
10984         strcat(bookMove, bookHit);
10985         savedMessage = bookMove; // args for deferred call
10986         savedState = onmove;
10987         ScheduleDelayedEvent(DeferredBookMove, 1);
10988     }
10989 }
10990
10991 void
10992 TrainingEvent()
10993 {
10994     if (gameMode == Training) {
10995       SetTrainingModeOff();
10996       gameMode = PlayFromGameFile;
10997       DisplayMessage("", _("Training mode off"));
10998     } else {
10999       gameMode = Training;
11000       animateTraining = appData.animate;
11001
11002       /* make sure we are not already at the end of the game */
11003       if (currentMove < forwardMostMove) {
11004         SetTrainingModeOn();
11005         DisplayMessage("", _("Training mode on"));
11006       } else {
11007         gameMode = PlayFromGameFile;
11008         DisplayError(_("Already at end of game"), 0);
11009       }
11010     }
11011     ModeHighlight();
11012 }
11013
11014 void
11015 IcsClientEvent()
11016 {
11017     if (!appData.icsActive) return;
11018     switch (gameMode) {
11019       case IcsPlayingWhite:
11020       case IcsPlayingBlack:
11021       case IcsObserving:
11022       case IcsIdle:
11023       case BeginningOfGame:
11024       case IcsExamining:
11025         return;
11026
11027       case EditGame:
11028         break;
11029
11030       case EditPosition:
11031         EditPositionDone();
11032         break;
11033
11034       case AnalyzeMode:
11035       case AnalyzeFile:
11036         ExitAnalyzeMode();
11037         break;
11038         
11039       default:
11040         EditGameEvent();
11041         break;
11042     }
11043
11044     gameMode = IcsIdle;
11045     ModeHighlight();
11046     return;
11047 }
11048
11049
11050 void
11051 EditGameEvent()
11052 {
11053     int i;
11054
11055     switch (gameMode) {
11056       case Training:
11057         SetTrainingModeOff();
11058         break;
11059       case MachinePlaysWhite:
11060       case MachinePlaysBlack:
11061       case BeginningOfGame:
11062         SendToProgram("force\n", &first);
11063         SetUserThinkingEnables();
11064         break;
11065       case PlayFromGameFile:
11066         (void) StopLoadGameTimer();
11067         if (gameFileFP != NULL) {
11068             gameFileFP = NULL;
11069         }
11070         break;
11071       case EditPosition:
11072         EditPositionDone();
11073         break;
11074       case AnalyzeMode:
11075       case AnalyzeFile:
11076         ExitAnalyzeMode();
11077         SendToProgram("force\n", &first);
11078         break;
11079       case TwoMachinesPlay:
11080         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11081         ResurrectChessProgram();
11082         SetUserThinkingEnables();
11083         break;
11084       case EndOfGame:
11085         ResurrectChessProgram();
11086         break;
11087       case IcsPlayingBlack:
11088       case IcsPlayingWhite:
11089         DisplayError(_("Warning: You are still playing a game"), 0);
11090         break;
11091       case IcsObserving:
11092         DisplayError(_("Warning: You are still observing a game"), 0);
11093         break;
11094       case IcsExamining:
11095         DisplayError(_("Warning: You are still examining a game"), 0);
11096         break;
11097       case IcsIdle:
11098         break;
11099       case EditGame:
11100       default:
11101         return;
11102     }
11103     
11104     pausing = FALSE;
11105     StopClocks();
11106     first.offeredDraw = second.offeredDraw = 0;
11107
11108     if (gameMode == PlayFromGameFile) {
11109         whiteTimeRemaining = timeRemaining[0][currentMove];
11110         blackTimeRemaining = timeRemaining[1][currentMove];
11111         DisplayTitle("");
11112     }
11113
11114     if (gameMode == MachinePlaysWhite ||
11115         gameMode == MachinePlaysBlack ||
11116         gameMode == TwoMachinesPlay ||
11117         gameMode == EndOfGame) {
11118         i = forwardMostMove;
11119         while (i > currentMove) {
11120             SendToProgram("undo\n", &first);
11121             i--;
11122         }
11123         whiteTimeRemaining = timeRemaining[0][currentMove];
11124         blackTimeRemaining = timeRemaining[1][currentMove];
11125         DisplayBothClocks();
11126         if (whiteFlag || blackFlag) {
11127             whiteFlag = blackFlag = 0;
11128         }
11129         DisplayTitle("");
11130     }           
11131     
11132     gameMode = EditGame;
11133     ModeHighlight();
11134     SetGameInfo();
11135 }
11136
11137
11138 void
11139 EditPositionEvent()
11140 {
11141     if (gameMode == EditPosition) {
11142         EditGameEvent();
11143         return;
11144     }
11145     
11146     EditGameEvent();
11147     if (gameMode != EditGame) return;
11148     
11149     gameMode = EditPosition;
11150     ModeHighlight();
11151     SetGameInfo();
11152     if (currentMove > 0)
11153       CopyBoard(boards[0], boards[currentMove]);
11154     
11155     blackPlaysFirst = !WhiteOnMove(currentMove);
11156     ResetClocks();
11157     currentMove = forwardMostMove = backwardMostMove = 0;
11158     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11159     DisplayMove(-1);
11160 }
11161
11162 void
11163 ExitAnalyzeMode()
11164 {
11165     /* [DM] icsEngineAnalyze - possible call from other functions */
11166     if (appData.icsEngineAnalyze) {
11167         appData.icsEngineAnalyze = FALSE;
11168
11169         DisplayMessage("",_("Close ICS engine analyze..."));
11170     }
11171     if (first.analysisSupport && first.analyzing) {
11172       SendToProgram("exit\n", &first);
11173       first.analyzing = FALSE;
11174     }
11175     thinkOutput[0] = NULLCHAR;
11176 }
11177
11178 void
11179 EditPositionDone()
11180 {
11181     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11182
11183     startedFromSetupPosition = TRUE;
11184     InitChessProgram(&first, FALSE);
11185     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11186     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11187         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11188         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11189     } else castlingRights[0][2] = -1;
11190     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11191         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11192         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11193     } else castlingRights[0][5] = -1;
11194     SendToProgram("force\n", &first);
11195     if (blackPlaysFirst) {
11196         strcpy(moveList[0], "");
11197         strcpy(parseList[0], "");
11198         currentMove = forwardMostMove = backwardMostMove = 1;
11199         CopyBoard(boards[1], boards[0]);
11200         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11201         { int i;
11202           epStatus[1] = epStatus[0];
11203           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11204         }
11205     } else {
11206         currentMove = forwardMostMove = backwardMostMove = 0;
11207     }
11208     SendBoard(&first, forwardMostMove);
11209     if (appData.debugMode) {
11210         fprintf(debugFP, "EditPosDone\n");
11211     }
11212     DisplayTitle("");
11213     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11214     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11215     gameMode = EditGame;
11216     ModeHighlight();
11217     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11218     ClearHighlights(); /* [AS] */
11219 }
11220
11221 /* Pause for `ms' milliseconds */
11222 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11223 void
11224 TimeDelay(ms)
11225      long ms;
11226 {
11227     TimeMark m1, m2;
11228
11229     GetTimeMark(&m1);
11230     do {
11231         GetTimeMark(&m2);
11232     } while (SubtractTimeMarks(&m2, &m1) < ms);
11233 }
11234
11235 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11236 void
11237 SendMultiLineToICS(buf)
11238      char *buf;
11239 {
11240     char temp[MSG_SIZ+1], *p;
11241     int len;
11242
11243     len = strlen(buf);
11244     if (len > MSG_SIZ)
11245       len = MSG_SIZ;
11246   
11247     strncpy(temp, buf, len);
11248     temp[len] = 0;
11249
11250     p = temp;
11251     while (*p) {
11252         if (*p == '\n' || *p == '\r')
11253           *p = ' ';
11254         ++p;
11255     }
11256
11257     strcat(temp, "\n");
11258     SendToICS(temp);
11259     SendToPlayer(temp, strlen(temp));
11260 }
11261
11262 void
11263 SetWhiteToPlayEvent()
11264 {
11265     if (gameMode == EditPosition) {
11266         blackPlaysFirst = FALSE;
11267         DisplayBothClocks();    /* works because currentMove is 0 */
11268     } else if (gameMode == IcsExamining) {
11269         SendToICS(ics_prefix);
11270         SendToICS("tomove white\n");
11271     }
11272 }
11273
11274 void
11275 SetBlackToPlayEvent()
11276 {
11277     if (gameMode == EditPosition) {
11278         blackPlaysFirst = TRUE;
11279         currentMove = 1;        /* kludge */
11280         DisplayBothClocks();
11281         currentMove = 0;
11282     } else if (gameMode == IcsExamining) {
11283         SendToICS(ics_prefix);
11284         SendToICS("tomove black\n");
11285     }
11286 }
11287
11288 void
11289 EditPositionMenuEvent(selection, x, y)
11290      ChessSquare selection;
11291      int x, y;
11292 {
11293     char buf[MSG_SIZ];
11294     ChessSquare piece = boards[0][y][x];
11295
11296     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11297
11298     switch (selection) {
11299       case ClearBoard:
11300         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11301             SendToICS(ics_prefix);
11302             SendToICS("bsetup clear\n");
11303         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11304             SendToICS(ics_prefix);
11305             SendToICS("clearboard\n");
11306         } else {
11307             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11308                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11309                 for (y = 0; y < BOARD_HEIGHT; y++) {
11310                     if (gameMode == IcsExamining) {
11311                         if (boards[currentMove][y][x] != EmptySquare) {
11312                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11313                                     AAA + x, ONE + y);
11314                             SendToICS(buf);
11315                         }
11316                     } else {
11317                         boards[0][y][x] = p;
11318                     }
11319                 }
11320             }
11321         }
11322         if (gameMode == EditPosition) {
11323             DrawPosition(FALSE, boards[0]);
11324         }
11325         break;
11326
11327       case WhitePlay:
11328         SetWhiteToPlayEvent();
11329         break;
11330
11331       case BlackPlay:
11332         SetBlackToPlayEvent();
11333         break;
11334
11335       case EmptySquare:
11336         if (gameMode == IcsExamining) {
11337             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11338             SendToICS(buf);
11339         } else {
11340             boards[0][y][x] = EmptySquare;
11341             DrawPosition(FALSE, boards[0]);
11342         }
11343         break;
11344
11345       case PromotePiece:
11346         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11347            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11348             selection = (ChessSquare) (PROMOTED piece);
11349         } else if(piece == EmptySquare) selection = WhiteSilver;
11350         else selection = (ChessSquare)((int)piece - 1);
11351         goto defaultlabel;
11352
11353       case DemotePiece:
11354         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11355            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11356             selection = (ChessSquare) (DEMOTED piece);
11357         } else if(piece == EmptySquare) selection = BlackSilver;
11358         else selection = (ChessSquare)((int)piece + 1);       
11359         goto defaultlabel;
11360
11361       case WhiteQueen:
11362       case BlackQueen:
11363         if(gameInfo.variant == VariantShatranj ||
11364            gameInfo.variant == VariantXiangqi  ||
11365            gameInfo.variant == VariantCourier    )
11366             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11367         goto defaultlabel;
11368
11369       case WhiteKing:
11370       case BlackKing:
11371         if(gameInfo.variant == VariantXiangqi)
11372             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11373         if(gameInfo.variant == VariantKnightmate)
11374             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11375       default:
11376         defaultlabel:
11377         if (gameMode == IcsExamining) {
11378             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11379                     PieceToChar(selection), AAA + x, ONE + y);
11380             SendToICS(buf);
11381         } else {
11382             boards[0][y][x] = selection;
11383             DrawPosition(FALSE, boards[0]);
11384         }
11385         break;
11386     }
11387 }
11388
11389
11390 void
11391 DropMenuEvent(selection, x, y)
11392      ChessSquare selection;
11393      int x, y;
11394 {
11395     ChessMove moveType;
11396
11397     switch (gameMode) {
11398       case IcsPlayingWhite:
11399       case MachinePlaysBlack:
11400         if (!WhiteOnMove(currentMove)) {
11401             DisplayMoveError(_("It is Black's turn"));
11402             return;
11403         }
11404         moveType = WhiteDrop;
11405         break;
11406       case IcsPlayingBlack:
11407       case MachinePlaysWhite:
11408         if (WhiteOnMove(currentMove)) {
11409             DisplayMoveError(_("It is White's turn"));
11410             return;
11411         }
11412         moveType = BlackDrop;
11413         break;
11414       case EditGame:
11415         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11416         break;
11417       default:
11418         return;
11419     }
11420
11421     if (moveType == BlackDrop && selection < BlackPawn) {
11422       selection = (ChessSquare) ((int) selection
11423                                  + (int) BlackPawn - (int) WhitePawn);
11424     }
11425     if (boards[currentMove][y][x] != EmptySquare) {
11426         DisplayMoveError(_("That square is occupied"));
11427         return;
11428     }
11429
11430     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11431 }
11432
11433 void
11434 AcceptEvent()
11435 {
11436     /* Accept a pending offer of any kind from opponent */
11437     
11438     if (appData.icsActive) {
11439         SendToICS(ics_prefix);
11440         SendToICS("accept\n");
11441     } else if (cmailMsgLoaded) {
11442         if (currentMove == cmailOldMove &&
11443             commentList[cmailOldMove] != NULL &&
11444             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11445                    "Black offers a draw" : "White offers a draw")) {
11446             TruncateGame();
11447             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11448             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11449         } else {
11450             DisplayError(_("There is no pending offer on this move"), 0);
11451             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11452         }
11453     } else {
11454         /* Not used for offers from chess program */
11455     }
11456 }
11457
11458 void
11459 DeclineEvent()
11460 {
11461     /* Decline a pending offer of any kind from opponent */
11462     
11463     if (appData.icsActive) {
11464         SendToICS(ics_prefix);
11465         SendToICS("decline\n");
11466     } else if (cmailMsgLoaded) {
11467         if (currentMove == cmailOldMove &&
11468             commentList[cmailOldMove] != NULL &&
11469             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11470                    "Black offers a draw" : "White offers a draw")) {
11471 #ifdef NOTDEF
11472             AppendComment(cmailOldMove, "Draw declined");
11473             DisplayComment(cmailOldMove - 1, "Draw declined");
11474 #endif /*NOTDEF*/
11475         } else {
11476             DisplayError(_("There is no pending offer on this move"), 0);
11477         }
11478     } else {
11479         /* Not used for offers from chess program */
11480     }
11481 }
11482
11483 void
11484 RematchEvent()
11485 {
11486     /* Issue ICS rematch command */
11487     if (appData.icsActive) {
11488         SendToICS(ics_prefix);
11489         SendToICS("rematch\n");
11490     }
11491 }
11492
11493 void
11494 CallFlagEvent()
11495 {
11496     /* Call your opponent's flag (claim a win on time) */
11497     if (appData.icsActive) {
11498         SendToICS(ics_prefix);
11499         SendToICS("flag\n");
11500     } else {
11501         switch (gameMode) {
11502           default:
11503             return;
11504           case MachinePlaysWhite:
11505             if (whiteFlag) {
11506                 if (blackFlag)
11507                   GameEnds(GameIsDrawn, "Both players ran out of time",
11508                            GE_PLAYER);
11509                 else
11510                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11511             } else {
11512                 DisplayError(_("Your opponent is not out of time"), 0);
11513             }
11514             break;
11515           case MachinePlaysBlack:
11516             if (blackFlag) {
11517                 if (whiteFlag)
11518                   GameEnds(GameIsDrawn, "Both players ran out of time",
11519                            GE_PLAYER);
11520                 else
11521                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11522             } else {
11523                 DisplayError(_("Your opponent is not out of time"), 0);
11524             }
11525             break;
11526         }
11527     }
11528 }
11529
11530 void
11531 DrawEvent()
11532 {
11533     /* Offer draw or accept pending draw offer from opponent */
11534     
11535     if (appData.icsActive) {
11536         /* Note: tournament rules require draw offers to be
11537            made after you make your move but before you punch
11538            your clock.  Currently ICS doesn't let you do that;
11539            instead, you immediately punch your clock after making
11540            a move, but you can offer a draw at any time. */
11541         
11542         SendToICS(ics_prefix);
11543         SendToICS("draw\n");
11544     } else if (cmailMsgLoaded) {
11545         if (currentMove == cmailOldMove &&
11546             commentList[cmailOldMove] != NULL &&
11547             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11548                    "Black offers a draw" : "White offers a draw")) {
11549             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11550             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11551         } else if (currentMove == cmailOldMove + 1) {
11552             char *offer = WhiteOnMove(cmailOldMove) ?
11553               "White offers a draw" : "Black offers a draw";
11554             AppendComment(currentMove, offer);
11555             DisplayComment(currentMove - 1, offer);
11556             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11557         } else {
11558             DisplayError(_("You must make your move before offering a draw"), 0);
11559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11560         }
11561     } else if (first.offeredDraw) {
11562         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11563     } else {
11564         if (first.sendDrawOffers) {
11565             SendToProgram("draw\n", &first);
11566             userOfferedDraw = TRUE;
11567         }
11568     }
11569 }
11570
11571 void
11572 AdjournEvent()
11573 {
11574     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11575     
11576     if (appData.icsActive) {
11577         SendToICS(ics_prefix);
11578         SendToICS("adjourn\n");
11579     } else {
11580         /* Currently GNU Chess doesn't offer or accept Adjourns */
11581     }
11582 }
11583
11584
11585 void
11586 AbortEvent()
11587 {
11588     /* Offer Abort or accept pending Abort offer from opponent */
11589     
11590     if (appData.icsActive) {
11591         SendToICS(ics_prefix);
11592         SendToICS("abort\n");
11593     } else {
11594         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11595     }
11596 }
11597
11598 void
11599 ResignEvent()
11600 {
11601     /* Resign.  You can do this even if it's not your turn. */
11602     
11603     if (appData.icsActive) {
11604         SendToICS(ics_prefix);
11605         SendToICS("resign\n");
11606     } else {
11607         switch (gameMode) {
11608           case MachinePlaysWhite:
11609             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11610             break;
11611           case MachinePlaysBlack:
11612             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11613             break;
11614           case EditGame:
11615             if (cmailMsgLoaded) {
11616                 TruncateGame();
11617                 if (WhiteOnMove(cmailOldMove)) {
11618                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11619                 } else {
11620                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11621                 }
11622                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11623             }
11624             break;
11625           default:
11626             break;
11627         }
11628     }
11629 }
11630
11631
11632 void
11633 StopObservingEvent()
11634 {
11635     /* Stop observing current games */
11636     SendToICS(ics_prefix);
11637     SendToICS("unobserve\n");
11638 }
11639
11640 void
11641 StopExaminingEvent()
11642 {
11643     /* Stop observing current game */
11644     SendToICS(ics_prefix);
11645     SendToICS("unexamine\n");
11646 }
11647
11648 void
11649 ForwardInner(target)
11650      int target;
11651 {
11652     int limit;
11653
11654     if (appData.debugMode)
11655         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11656                 target, currentMove, forwardMostMove);
11657
11658     if (gameMode == EditPosition)
11659       return;
11660
11661     if (gameMode == PlayFromGameFile && !pausing)
11662       PauseEvent();
11663     
11664     if (gameMode == IcsExamining && pausing)
11665       limit = pauseExamForwardMostMove;
11666     else
11667       limit = forwardMostMove;
11668     
11669     if (target > limit) target = limit;
11670
11671     if (target > 0 && moveList[target - 1][0]) {
11672         int fromX, fromY, toX, toY;
11673         toX = moveList[target - 1][2] - AAA;
11674         toY = moveList[target - 1][3] - ONE;
11675         if (moveList[target - 1][1] == '@') {
11676             if (appData.highlightLastMove) {
11677                 SetHighlights(-1, -1, toX, toY);
11678             }
11679         } else {
11680             fromX = moveList[target - 1][0] - AAA;
11681             fromY = moveList[target - 1][1] - ONE;
11682             if (target == currentMove + 1) {
11683                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11684             }
11685             if (appData.highlightLastMove) {
11686                 SetHighlights(fromX, fromY, toX, toY);
11687             }
11688         }
11689     }
11690     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11691         gameMode == Training || gameMode == PlayFromGameFile || 
11692         gameMode == AnalyzeFile) {
11693         while (currentMove < target) {
11694             SendMoveToProgram(currentMove++, &first);
11695         }
11696     } else {
11697         currentMove = target;
11698     }
11699     
11700     if (gameMode == EditGame || gameMode == EndOfGame) {
11701         whiteTimeRemaining = timeRemaining[0][currentMove];
11702         blackTimeRemaining = timeRemaining[1][currentMove];
11703     }
11704     DisplayBothClocks();
11705     DisplayMove(currentMove - 1);
11706     DrawPosition(FALSE, boards[currentMove]);
11707     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11708     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11709         DisplayComment(currentMove - 1, commentList[currentMove]);
11710     }
11711 }
11712
11713
11714 void
11715 ForwardEvent()
11716 {
11717     if (gameMode == IcsExamining && !pausing) {
11718         SendToICS(ics_prefix);
11719         SendToICS("forward\n");
11720     } else {
11721         ForwardInner(currentMove + 1);
11722     }
11723 }
11724
11725 void
11726 ToEndEvent()
11727 {
11728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11729         /* to optimze, we temporarily turn off analysis mode while we feed
11730          * the remaining moves to the engine. Otherwise we get analysis output
11731          * after each move.
11732          */ 
11733         if (first.analysisSupport) {
11734           SendToProgram("exit\nforce\n", &first);
11735           first.analyzing = FALSE;
11736         }
11737     }
11738         
11739     if (gameMode == IcsExamining && !pausing) {
11740         SendToICS(ics_prefix);
11741         SendToICS("forward 999999\n");
11742     } else {
11743         ForwardInner(forwardMostMove);
11744     }
11745
11746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11747         /* we have fed all the moves, so reactivate analysis mode */
11748         SendToProgram("analyze\n", &first);
11749         first.analyzing = TRUE;
11750         /*first.maybeThinking = TRUE;*/
11751         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11752     }
11753 }
11754
11755 void
11756 BackwardInner(target)
11757      int target;
11758 {
11759     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11760
11761     if (appData.debugMode)
11762         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11763                 target, currentMove, forwardMostMove);
11764
11765     if (gameMode == EditPosition) return;
11766     if (currentMove <= backwardMostMove) {
11767         ClearHighlights();
11768         DrawPosition(full_redraw, boards[currentMove]);
11769         return;
11770     }
11771     if (gameMode == PlayFromGameFile && !pausing)
11772       PauseEvent();
11773     
11774     if (moveList[target][0]) {
11775         int fromX, fromY, toX, toY;
11776         toX = moveList[target][2] - AAA;
11777         toY = moveList[target][3] - ONE;
11778         if (moveList[target][1] == '@') {
11779             if (appData.highlightLastMove) {
11780                 SetHighlights(-1, -1, toX, toY);
11781             }
11782         } else {
11783             fromX = moveList[target][0] - AAA;
11784             fromY = moveList[target][1] - ONE;
11785             if (target == currentMove - 1) {
11786                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11787             }
11788             if (appData.highlightLastMove) {
11789                 SetHighlights(fromX, fromY, toX, toY);
11790             }
11791         }
11792     }
11793     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11794         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11795         while (currentMove > target) {
11796             SendToProgram("undo\n", &first);
11797             currentMove--;
11798         }
11799     } else {
11800         currentMove = target;
11801     }
11802     
11803     if (gameMode == EditGame || gameMode == EndOfGame) {
11804         whiteTimeRemaining = timeRemaining[0][currentMove];
11805         blackTimeRemaining = timeRemaining[1][currentMove];
11806     }
11807     DisplayBothClocks();
11808     DisplayMove(currentMove - 1);
11809     DrawPosition(full_redraw, boards[currentMove]);
11810     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11811     // [HGM] PV info: routine tests if comment empty
11812     DisplayComment(currentMove - 1, commentList[currentMove]);
11813 }
11814
11815 void
11816 BackwardEvent()
11817 {
11818     if (gameMode == IcsExamining && !pausing) {
11819         SendToICS(ics_prefix);
11820         SendToICS("backward\n");
11821     } else {
11822         BackwardInner(currentMove - 1);
11823     }
11824 }
11825
11826 void
11827 ToStartEvent()
11828 {
11829     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11830         /* to optimze, we temporarily turn off analysis mode while we undo
11831          * all the moves. Otherwise we get analysis output after each undo.
11832          */ 
11833         if (first.analysisSupport) {
11834           SendToProgram("exit\nforce\n", &first);
11835           first.analyzing = FALSE;
11836         }
11837     }
11838
11839     if (gameMode == IcsExamining && !pausing) {
11840         SendToICS(ics_prefix);
11841         SendToICS("backward 999999\n");
11842     } else {
11843         BackwardInner(backwardMostMove);
11844     }
11845
11846     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11847         /* we have fed all the moves, so reactivate analysis mode */
11848         SendToProgram("analyze\n", &first);
11849         first.analyzing = TRUE;
11850         /*first.maybeThinking = TRUE;*/
11851         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11852     }
11853 }
11854
11855 void
11856 ToNrEvent(int to)
11857 {
11858   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11859   if (to >= forwardMostMove) to = forwardMostMove;
11860   if (to <= backwardMostMove) to = backwardMostMove;
11861   if (to < currentMove) {
11862     BackwardInner(to);
11863   } else {
11864     ForwardInner(to);
11865   }
11866 }
11867
11868 void
11869 RevertEvent()
11870 {
11871     if (gameMode != IcsExamining) {
11872         DisplayError(_("You are not examining a game"), 0);
11873         return;
11874     }
11875     if (pausing) {
11876         DisplayError(_("You can't revert while pausing"), 0);
11877         return;
11878     }
11879     SendToICS(ics_prefix);
11880     SendToICS("revert\n");
11881 }
11882
11883 void
11884 RetractMoveEvent()
11885 {
11886     switch (gameMode) {
11887       case MachinePlaysWhite:
11888       case MachinePlaysBlack:
11889         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11890             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11891             return;
11892         }
11893         if (forwardMostMove < 2) return;
11894         currentMove = forwardMostMove = forwardMostMove - 2;
11895         whiteTimeRemaining = timeRemaining[0][currentMove];
11896         blackTimeRemaining = timeRemaining[1][currentMove];
11897         DisplayBothClocks();
11898         DisplayMove(currentMove - 1);
11899         ClearHighlights();/*!! could figure this out*/
11900         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11901         SendToProgram("remove\n", &first);
11902         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11903         break;
11904
11905       case BeginningOfGame:
11906       default:
11907         break;
11908
11909       case IcsPlayingWhite:
11910       case IcsPlayingBlack:
11911         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11912             SendToICS(ics_prefix);
11913             SendToICS("takeback 2\n");
11914         } else {
11915             SendToICS(ics_prefix);
11916             SendToICS("takeback 1\n");
11917         }
11918         break;
11919     }
11920 }
11921
11922 void
11923 MoveNowEvent()
11924 {
11925     ChessProgramState *cps;
11926
11927     switch (gameMode) {
11928       case MachinePlaysWhite:
11929         if (!WhiteOnMove(forwardMostMove)) {
11930             DisplayError(_("It is your turn"), 0);
11931             return;
11932         }
11933         cps = &first;
11934         break;
11935       case MachinePlaysBlack:
11936         if (WhiteOnMove(forwardMostMove)) {
11937             DisplayError(_("It is your turn"), 0);
11938             return;
11939         }
11940         cps = &first;
11941         break;
11942       case TwoMachinesPlay:
11943         if (WhiteOnMove(forwardMostMove) ==
11944             (first.twoMachinesColor[0] == 'w')) {
11945             cps = &first;
11946         } else {
11947             cps = &second;
11948         }
11949         break;
11950       case BeginningOfGame:
11951       default:
11952         return;
11953     }
11954     SendToProgram("?\n", cps);
11955 }
11956
11957 void
11958 TruncateGameEvent()
11959 {
11960     EditGameEvent();
11961     if (gameMode != EditGame) return;
11962     TruncateGame();
11963 }
11964
11965 void
11966 TruncateGame()
11967 {
11968     if (forwardMostMove > currentMove) {
11969         if (gameInfo.resultDetails != NULL) {
11970             free(gameInfo.resultDetails);
11971             gameInfo.resultDetails = NULL;
11972             gameInfo.result = GameUnfinished;
11973         }
11974         forwardMostMove = currentMove;
11975         HistorySet(parseList, backwardMostMove, forwardMostMove,
11976                    currentMove-1);
11977     }
11978 }
11979
11980 void
11981 HintEvent()
11982 {
11983     if (appData.noChessProgram) return;
11984     switch (gameMode) {
11985       case MachinePlaysWhite:
11986         if (WhiteOnMove(forwardMostMove)) {
11987             DisplayError(_("Wait until your turn"), 0);
11988             return;
11989         }
11990         break;
11991       case BeginningOfGame:
11992       case MachinePlaysBlack:
11993         if (!WhiteOnMove(forwardMostMove)) {
11994             DisplayError(_("Wait until your turn"), 0);
11995             return;
11996         }
11997         break;
11998       default:
11999         DisplayError(_("No hint available"), 0);
12000         return;
12001     }
12002     SendToProgram("hint\n", &first);
12003     hintRequested = TRUE;
12004 }
12005
12006 void
12007 BookEvent()
12008 {
12009     if (appData.noChessProgram) return;
12010     switch (gameMode) {
12011       case MachinePlaysWhite:
12012         if (WhiteOnMove(forwardMostMove)) {
12013             DisplayError(_("Wait until your turn"), 0);
12014             return;
12015         }
12016         break;
12017       case BeginningOfGame:
12018       case MachinePlaysBlack:
12019         if (!WhiteOnMove(forwardMostMove)) {
12020             DisplayError(_("Wait until your turn"), 0);
12021             return;
12022         }
12023         break;
12024       case EditPosition:
12025         EditPositionDone();
12026         break;
12027       case TwoMachinesPlay:
12028         return;
12029       default:
12030         break;
12031     }
12032     SendToProgram("bk\n", &first);
12033     bookOutput[0] = NULLCHAR;
12034     bookRequested = TRUE;
12035 }
12036
12037 void
12038 AboutGameEvent()
12039 {
12040     char *tags = PGNTags(&gameInfo);
12041     TagsPopUp(tags, CmailMsg());
12042     free(tags);
12043 }
12044
12045 /* end button procedures */
12046
12047 void
12048 PrintPosition(fp, move)
12049      FILE *fp;
12050      int move;
12051 {
12052     int i, j;
12053     
12054     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12055         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12056             char c = PieceToChar(boards[move][i][j]);
12057             fputc(c == 'x' ? '.' : c, fp);
12058             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12059         }
12060     }
12061     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12062       fprintf(fp, "white to play\n");
12063     else
12064       fprintf(fp, "black to play\n");
12065 }
12066
12067 void
12068 PrintOpponents(fp)
12069      FILE *fp;
12070 {
12071     if (gameInfo.white != NULL) {
12072         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12073     } else {
12074         fprintf(fp, "\n");
12075     }
12076 }
12077
12078 /* Find last component of program's own name, using some heuristics */
12079 void
12080 TidyProgramName(prog, host, buf)
12081      char *prog, *host, buf[MSG_SIZ];
12082 {
12083     char *p, *q;
12084     int local = (strcmp(host, "localhost") == 0);
12085     while (!local && (p = strchr(prog, ';')) != NULL) {
12086         p++;
12087         while (*p == ' ') p++;
12088         prog = p;
12089     }
12090     if (*prog == '"' || *prog == '\'') {
12091         q = strchr(prog + 1, *prog);
12092     } else {
12093         q = strchr(prog, ' ');
12094     }
12095     if (q == NULL) q = prog + strlen(prog);
12096     p = q;
12097     while (p >= prog && *p != '/' && *p != '\\') p--;
12098     p++;
12099     if(p == prog && *p == '"') p++;
12100     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12101     memcpy(buf, p, q - p);
12102     buf[q - p] = NULLCHAR;
12103     if (!local) {
12104         strcat(buf, "@");
12105         strcat(buf, host);
12106     }
12107 }
12108
12109 char *
12110 TimeControlTagValue()
12111 {
12112     char buf[MSG_SIZ];
12113     if (!appData.clockMode) {
12114         strcpy(buf, "-");
12115     } else if (movesPerSession > 0) {
12116         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12117     } else if (timeIncrement == 0) {
12118         sprintf(buf, "%ld", timeControl/1000);
12119     } else {
12120         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12121     }
12122     return StrSave(buf);
12123 }
12124
12125 void
12126 SetGameInfo()
12127 {
12128     /* This routine is used only for certain modes */
12129     VariantClass v = gameInfo.variant;
12130     ClearGameInfo(&gameInfo);
12131     gameInfo.variant = v;
12132
12133     switch (gameMode) {
12134       case MachinePlaysWhite:
12135         gameInfo.event = StrSave( appData.pgnEventHeader );
12136         gameInfo.site = StrSave(HostName());
12137         gameInfo.date = PGNDate();
12138         gameInfo.round = StrSave("-");
12139         gameInfo.white = StrSave(first.tidy);
12140         gameInfo.black = StrSave(UserName());
12141         gameInfo.timeControl = TimeControlTagValue();
12142         break;
12143
12144       case MachinePlaysBlack:
12145         gameInfo.event = StrSave( appData.pgnEventHeader );
12146         gameInfo.site = StrSave(HostName());
12147         gameInfo.date = PGNDate();
12148         gameInfo.round = StrSave("-");
12149         gameInfo.white = StrSave(UserName());
12150         gameInfo.black = StrSave(first.tidy);
12151         gameInfo.timeControl = TimeControlTagValue();
12152         break;
12153
12154       case TwoMachinesPlay:
12155         gameInfo.event = StrSave( appData.pgnEventHeader );
12156         gameInfo.site = StrSave(HostName());
12157         gameInfo.date = PGNDate();
12158         if (matchGame > 0) {
12159             char buf[MSG_SIZ];
12160             sprintf(buf, "%d", matchGame);
12161             gameInfo.round = StrSave(buf);
12162         } else {
12163             gameInfo.round = StrSave("-");
12164         }
12165         if (first.twoMachinesColor[0] == 'w') {
12166             gameInfo.white = StrSave(first.tidy);
12167             gameInfo.black = StrSave(second.tidy);
12168         } else {
12169             gameInfo.white = StrSave(second.tidy);
12170             gameInfo.black = StrSave(first.tidy);
12171         }
12172         gameInfo.timeControl = TimeControlTagValue();
12173         break;
12174
12175       case EditGame:
12176         gameInfo.event = StrSave("Edited game");
12177         gameInfo.site = StrSave(HostName());
12178         gameInfo.date = PGNDate();
12179         gameInfo.round = StrSave("-");
12180         gameInfo.white = StrSave("-");
12181         gameInfo.black = StrSave("-");
12182         break;
12183
12184       case EditPosition:
12185         gameInfo.event = StrSave("Edited position");
12186         gameInfo.site = StrSave(HostName());
12187         gameInfo.date = PGNDate();
12188         gameInfo.round = StrSave("-");
12189         gameInfo.white = StrSave("-");
12190         gameInfo.black = StrSave("-");
12191         break;
12192
12193       case IcsPlayingWhite:
12194       case IcsPlayingBlack:
12195       case IcsObserving:
12196       case IcsExamining:
12197         break;
12198
12199       case PlayFromGameFile:
12200         gameInfo.event = StrSave("Game from non-PGN file");
12201         gameInfo.site = StrSave(HostName());
12202         gameInfo.date = PGNDate();
12203         gameInfo.round = StrSave("-");
12204         gameInfo.white = StrSave("?");
12205         gameInfo.black = StrSave("?");
12206         break;
12207
12208       default:
12209         break;
12210     }
12211 }
12212
12213 void
12214 ReplaceComment(index, text)
12215      int index;
12216      char *text;
12217 {
12218     int len;
12219
12220     while (*text == '\n') text++;
12221     len = strlen(text);
12222     while (len > 0 && text[len - 1] == '\n') len--;
12223
12224     if (commentList[index] != NULL)
12225       free(commentList[index]);
12226
12227     if (len == 0) {
12228         commentList[index] = NULL;
12229         return;
12230     }
12231     commentList[index] = (char *) malloc(len + 2);
12232     strncpy(commentList[index], text, len);
12233     commentList[index][len] = '\n';
12234     commentList[index][len + 1] = NULLCHAR;
12235 }
12236
12237 void
12238 CrushCRs(text)
12239      char *text;
12240 {
12241   char *p = text;
12242   char *q = text;
12243   char ch;
12244
12245   do {
12246     ch = *p++;
12247     if (ch == '\r') continue;
12248     *q++ = ch;
12249   } while (ch != '\0');
12250 }
12251
12252 void
12253 AppendComment(index, text)
12254      int index;
12255      char *text;
12256 {
12257     int oldlen, len;
12258     char *old;
12259
12260     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12261
12262     CrushCRs(text);
12263     while (*text == '\n') text++;
12264     len = strlen(text);
12265     while (len > 0 && text[len - 1] == '\n') len--;
12266
12267     if (len == 0) return;
12268
12269     if (commentList[index] != NULL) {
12270         old = commentList[index];
12271         oldlen = strlen(old);
12272         commentList[index] = (char *) malloc(oldlen + len + 2);
12273         strcpy(commentList[index], old);
12274         free(old);
12275         strncpy(&commentList[index][oldlen], text, len);
12276         commentList[index][oldlen + len] = '\n';
12277         commentList[index][oldlen + len + 1] = NULLCHAR;
12278     } else {
12279         commentList[index] = (char *) malloc(len + 2);
12280         strncpy(commentList[index], text, len);
12281         commentList[index][len] = '\n';
12282         commentList[index][len + 1] = NULLCHAR;
12283     }
12284 }
12285
12286 static char * FindStr( char * text, char * sub_text )
12287 {
12288     char * result = strstr( text, sub_text );
12289
12290     if( result != NULL ) {
12291         result += strlen( sub_text );
12292     }
12293
12294     return result;
12295 }
12296
12297 /* [AS] Try to extract PV info from PGN comment */
12298 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12299 char *GetInfoFromComment( int index, char * text )
12300 {
12301     char * sep = text;
12302
12303     if( text != NULL && index > 0 ) {
12304         int score = 0;
12305         int depth = 0;
12306         int time = -1, sec = 0, deci;
12307         char * s_eval = FindStr( text, "[%eval " );
12308         char * s_emt = FindStr( text, "[%emt " );
12309
12310         if( s_eval != NULL || s_emt != NULL ) {
12311             /* New style */
12312             char delim;
12313
12314             if( s_eval != NULL ) {
12315                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12316                     return text;
12317                 }
12318
12319                 if( delim != ']' ) {
12320                     return text;
12321                 }
12322             }
12323
12324             if( s_emt != NULL ) {
12325             }
12326         }
12327         else {
12328             /* We expect something like: [+|-]nnn.nn/dd */
12329             int score_lo = 0;
12330
12331             sep = strchr( text, '/' );
12332             if( sep == NULL || sep < (text+4) ) {
12333                 return text;
12334             }
12335
12336             time = -1; sec = -1; deci = -1;
12337             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12338                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12339                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12340                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12341                 return text;
12342             }
12343
12344             if( score_lo < 0 || score_lo >= 100 ) {
12345                 return text;
12346             }
12347
12348             if(sec >= 0) time = 600*time + 10*sec; else
12349             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12350
12351             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12352
12353             /* [HGM] PV time: now locate end of PV info */
12354             while( *++sep >= '0' && *sep <= '9'); // strip depth
12355             if(time >= 0)
12356             while( *++sep >= '0' && *sep <= '9'); // strip time
12357             if(sec >= 0)
12358             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12359             if(deci >= 0)
12360             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12361             while(*sep == ' ') sep++;
12362         }
12363
12364         if( depth <= 0 ) {
12365             return text;
12366         }
12367
12368         if( time < 0 ) {
12369             time = -1;
12370         }
12371
12372         pvInfoList[index-1].depth = depth;
12373         pvInfoList[index-1].score = score;
12374         pvInfoList[index-1].time  = 10*time; // centi-sec
12375     }
12376     return sep;
12377 }
12378
12379 void
12380 SendToProgram(message, cps)
12381      char *message;
12382      ChessProgramState *cps;
12383 {
12384     int count, outCount, error;
12385     char buf[MSG_SIZ];
12386
12387     if (cps->pr == NULL) return;
12388     Attention(cps);
12389     
12390     if (appData.debugMode) {
12391         TimeMark now;
12392         GetTimeMark(&now);
12393         fprintf(debugFP, "%ld >%-6s: %s", 
12394                 SubtractTimeMarks(&now, &programStartTime),
12395                 cps->which, message);
12396     }
12397     
12398     count = strlen(message);
12399     outCount = OutputToProcess(cps->pr, message, count, &error);
12400     if (outCount < count && !exiting 
12401                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12402         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12403         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12404             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12405                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12406                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12407             } else {
12408                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12409             }
12410             gameInfo.resultDetails = buf;
12411         }
12412         DisplayFatalError(buf, error, 1);
12413     }
12414 }
12415
12416 void
12417 ReceiveFromProgram(isr, closure, message, count, error)
12418      InputSourceRef isr;
12419      VOIDSTAR closure;
12420      char *message;
12421      int count;
12422      int error;
12423 {
12424     char *end_str;
12425     char buf[MSG_SIZ];
12426     ChessProgramState *cps = (ChessProgramState *)closure;
12427
12428     if (isr != cps->isr) return; /* Killed intentionally */
12429     if (count <= 0) {
12430         if (count == 0) {
12431             sprintf(buf,
12432                     _("Error: %s chess program (%s) exited unexpectedly"),
12433                     cps->which, cps->program);
12434         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12435                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12436                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12437                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12438                 } else {
12439                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12440                 }
12441                 gameInfo.resultDetails = buf;
12442             }
12443             RemoveInputSource(cps->isr);
12444             DisplayFatalError(buf, 0, 1);
12445         } else {
12446             sprintf(buf,
12447                     _("Error reading from %s chess program (%s)"),
12448                     cps->which, cps->program);
12449             RemoveInputSource(cps->isr);
12450
12451             /* [AS] Program is misbehaving badly... kill it */
12452             if( count == -2 ) {
12453                 DestroyChildProcess( cps->pr, 9 );
12454                 cps->pr = NoProc;
12455             }
12456
12457             DisplayFatalError(buf, error, 1);
12458         }
12459         return;
12460     }
12461     
12462     if ((end_str = strchr(message, '\r')) != NULL)
12463       *end_str = NULLCHAR;
12464     if ((end_str = strchr(message, '\n')) != NULL)
12465       *end_str = NULLCHAR;
12466     
12467     if (appData.debugMode) {
12468         TimeMark now; int print = 1;
12469         char *quote = ""; char c; int i;
12470
12471         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12472                 char start = message[0];
12473                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12474                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12475                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12476                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12477                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12478                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12479                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12480                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12481                         { quote = "# "; print = (appData.engineComments == 2); }
12482                 message[0] = start; // restore original message
12483         }
12484         if(print) {
12485                 GetTimeMark(&now);
12486                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12487                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12488                         quote,
12489                         message);
12490         }
12491     }
12492
12493     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12494     if (appData.icsEngineAnalyze) {
12495         if (strstr(message, "whisper") != NULL ||
12496              strstr(message, "kibitz") != NULL || 
12497             strstr(message, "tellics") != NULL) return;
12498     }
12499
12500     HandleMachineMove(message, cps);
12501 }
12502
12503
12504 void
12505 SendTimeControl(cps, mps, tc, inc, sd, st)
12506      ChessProgramState *cps;
12507      int mps, inc, sd, st;
12508      long tc;
12509 {
12510     char buf[MSG_SIZ];
12511     int seconds;
12512
12513     if( timeControl_2 > 0 ) {
12514         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12515             tc = timeControl_2;
12516         }
12517     }
12518     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12519     inc /= cps->timeOdds;
12520     st  /= cps->timeOdds;
12521
12522     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12523
12524     if (st > 0) {
12525       /* Set exact time per move, normally using st command */
12526       if (cps->stKludge) {
12527         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12528         seconds = st % 60;
12529         if (seconds == 0) {
12530           sprintf(buf, "level 1 %d\n", st/60);
12531         } else {
12532           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12533         }
12534       } else {
12535         sprintf(buf, "st %d\n", st);
12536       }
12537     } else {
12538       /* Set conventional or incremental time control, using level command */
12539       if (seconds == 0) {
12540         /* Note old gnuchess bug -- minutes:seconds used to not work.
12541            Fixed in later versions, but still avoid :seconds
12542            when seconds is 0. */
12543         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12544       } else {
12545         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12546                 seconds, inc/1000);
12547       }
12548     }
12549     SendToProgram(buf, cps);
12550
12551     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12552     /* Orthogonally, limit search to given depth */
12553     if (sd > 0) {
12554       if (cps->sdKludge) {
12555         sprintf(buf, "depth\n%d\n", sd);
12556       } else {
12557         sprintf(buf, "sd %d\n", sd);
12558       }
12559       SendToProgram(buf, cps);
12560     }
12561
12562     if(cps->nps > 0) { /* [HGM] nps */
12563         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12564         else {
12565                 sprintf(buf, "nps %d\n", cps->nps);
12566               SendToProgram(buf, cps);
12567         }
12568     }
12569 }
12570
12571 ChessProgramState *WhitePlayer()
12572 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12573 {
12574     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12575        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12576         return &second;
12577     return &first;
12578 }
12579
12580 void
12581 SendTimeRemaining(cps, machineWhite)
12582      ChessProgramState *cps;
12583      int /*boolean*/ machineWhite;
12584 {
12585     char message[MSG_SIZ];
12586     long time, otime;
12587
12588     /* Note: this routine must be called when the clocks are stopped
12589        or when they have *just* been set or switched; otherwise
12590        it will be off by the time since the current tick started.
12591     */
12592     if (machineWhite) {
12593         time = whiteTimeRemaining / 10;
12594         otime = blackTimeRemaining / 10;
12595     } else {
12596         time = blackTimeRemaining / 10;
12597         otime = whiteTimeRemaining / 10;
12598     }
12599     /* [HGM] translate opponent's time by time-odds factor */
12600     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12601     if (appData.debugMode) {
12602         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12603     }
12604
12605     if (time <= 0) time = 1;
12606     if (otime <= 0) otime = 1;
12607     
12608     sprintf(message, "time %ld\n", time);
12609     SendToProgram(message, cps);
12610
12611     sprintf(message, "otim %ld\n", otime);
12612     SendToProgram(message, cps);
12613 }
12614
12615 int
12616 BoolFeature(p, name, loc, cps)
12617      char **p;
12618      char *name;
12619      int *loc;
12620      ChessProgramState *cps;
12621 {
12622   char buf[MSG_SIZ];
12623   int len = strlen(name);
12624   int val;
12625   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12626     (*p) += len + 1;
12627     sscanf(*p, "%d", &val);
12628     *loc = (val != 0);
12629     while (**p && **p != ' ') (*p)++;
12630     sprintf(buf, "accepted %s\n", name);
12631     SendToProgram(buf, cps);
12632     return TRUE;
12633   }
12634   return FALSE;
12635 }
12636
12637 int
12638 IntFeature(p, name, loc, cps)
12639      char **p;
12640      char *name;
12641      int *loc;
12642      ChessProgramState *cps;
12643 {
12644   char buf[MSG_SIZ];
12645   int len = strlen(name);
12646   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12647     (*p) += len + 1;
12648     sscanf(*p, "%d", loc);
12649     while (**p && **p != ' ') (*p)++;
12650     sprintf(buf, "accepted %s\n", name);
12651     SendToProgram(buf, cps);
12652     return TRUE;
12653   }
12654   return FALSE;
12655 }
12656
12657 int
12658 StringFeature(p, name, loc, cps)
12659      char **p;
12660      char *name;
12661      char loc[];
12662      ChessProgramState *cps;
12663 {
12664   char buf[MSG_SIZ];
12665   int len = strlen(name);
12666   if (strncmp((*p), name, len) == 0
12667       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12668     (*p) += len + 2;
12669     sscanf(*p, "%[^\"]", loc);
12670     while (**p && **p != '\"') (*p)++;
12671     if (**p == '\"') (*p)++;
12672     sprintf(buf, "accepted %s\n", name);
12673     SendToProgram(buf, cps);
12674     return TRUE;
12675   }
12676   return FALSE;
12677 }
12678
12679 int 
12680 ParseOption(Option *opt, ChessProgramState *cps)
12681 // [HGM] options: process the string that defines an engine option, and determine
12682 // name, type, default value, and allowed value range
12683 {
12684         char *p, *q, buf[MSG_SIZ];
12685         int n, min = (-1)<<31, max = 1<<31, def;
12686
12687         if(p = strstr(opt->name, " -spin ")) {
12688             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12689             if(max < min) max = min; // enforce consistency
12690             if(def < min) def = min;
12691             if(def > max) def = max;
12692             opt->value = def;
12693             opt->min = min;
12694             opt->max = max;
12695             opt->type = Spin;
12696         } else if((p = strstr(opt->name, " -slider "))) {
12697             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12698             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12699             if(max < min) max = min; // enforce consistency
12700             if(def < min) def = min;
12701             if(def > max) def = max;
12702             opt->value = def;
12703             opt->min = min;
12704             opt->max = max;
12705             opt->type = Spin; // Slider;
12706         } else if((p = strstr(opt->name, " -string "))) {
12707             opt->textValue = p+9;
12708             opt->type = TextBox;
12709         } else if((p = strstr(opt->name, " -file "))) {
12710             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12711             opt->textValue = p+7;
12712             opt->type = TextBox; // FileName;
12713         } else if((p = strstr(opt->name, " -path "))) {
12714             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12715             opt->textValue = p+7;
12716             opt->type = TextBox; // PathName;
12717         } else if(p = strstr(opt->name, " -check ")) {
12718             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12719             opt->value = (def != 0);
12720             opt->type = CheckBox;
12721         } else if(p = strstr(opt->name, " -combo ")) {
12722             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12723             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12724             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12725             opt->value = n = 0;
12726             while(q = StrStr(q, " /// ")) {
12727                 n++; *q = 0;    // count choices, and null-terminate each of them
12728                 q += 5;
12729                 if(*q == '*') { // remember default, which is marked with * prefix
12730                     q++;
12731                     opt->value = n;
12732                 }
12733                 cps->comboList[cps->comboCnt++] = q;
12734             }
12735             cps->comboList[cps->comboCnt++] = NULL;
12736             opt->max = n + 1;
12737             opt->type = ComboBox;
12738         } else if(p = strstr(opt->name, " -button")) {
12739             opt->type = Button;
12740         } else if(p = strstr(opt->name, " -save")) {
12741             opt->type = SaveButton;
12742         } else return FALSE;
12743         *p = 0; // terminate option name
12744         // now look if the command-line options define a setting for this engine option.
12745         if(cps->optionSettings && cps->optionSettings[0])
12746             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12747         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12748                 sprintf(buf, "option %s", p);
12749                 if(p = strstr(buf, ",")) *p = 0;
12750                 strcat(buf, "\n");
12751                 SendToProgram(buf, cps);
12752         }
12753         return TRUE;
12754 }
12755
12756 void
12757 FeatureDone(cps, val)
12758      ChessProgramState* cps;
12759      int val;
12760 {
12761   DelayedEventCallback cb = GetDelayedEvent();
12762   if ((cb == InitBackEnd3 && cps == &first) ||
12763       (cb == TwoMachinesEventIfReady && cps == &second)) {
12764     CancelDelayedEvent();
12765     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12766   }
12767   cps->initDone = val;
12768 }
12769
12770 /* Parse feature command from engine */
12771 void
12772 ParseFeatures(args, cps)
12773      char* args;
12774      ChessProgramState *cps;  
12775 {
12776   char *p = args;
12777   char *q;
12778   int val;
12779   char buf[MSG_SIZ];
12780
12781   for (;;) {
12782     while (*p == ' ') p++;
12783     if (*p == NULLCHAR) return;
12784
12785     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12786     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12787     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12788     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12789     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12790     if (BoolFeature(&p, "reuse", &val, cps)) {
12791       /* Engine can disable reuse, but can't enable it if user said no */
12792       if (!val) cps->reuse = FALSE;
12793       continue;
12794     }
12795     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12796     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12797       if (gameMode == TwoMachinesPlay) {
12798         DisplayTwoMachinesTitle();
12799       } else {
12800         DisplayTitle("");
12801       }
12802       continue;
12803     }
12804     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12805     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12806     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12807     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12808     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12809     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12810     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12811     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12812     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12813     if (IntFeature(&p, "done", &val, cps)) {
12814       FeatureDone(cps, val);
12815       continue;
12816     }
12817     /* Added by Tord: */
12818     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12819     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12820     /* End of additions by Tord */
12821
12822     /* [HGM] added features: */
12823     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12824     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12825     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12826     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12827     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12828     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12829     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12830         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12831             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12832             SendToProgram(buf, cps);
12833             continue;
12834         }
12835         if(cps->nrOptions >= MAX_OPTIONS) {
12836             cps->nrOptions--;
12837             sprintf(buf, "%s engine has too many options\n", cps->which);
12838             DisplayError(buf, 0);
12839         }
12840         continue;
12841     }
12842     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12843     /* End of additions by HGM */
12844
12845     /* unknown feature: complain and skip */
12846     q = p;
12847     while (*q && *q != '=') q++;
12848     sprintf(buf, "rejected %.*s\n", q-p, p);
12849     SendToProgram(buf, cps);
12850     p = q;
12851     if (*p == '=') {
12852       p++;
12853       if (*p == '\"') {
12854         p++;
12855         while (*p && *p != '\"') p++;
12856         if (*p == '\"') p++;
12857       } else {
12858         while (*p && *p != ' ') p++;
12859       }
12860     }
12861   }
12862
12863 }
12864
12865 void
12866 PeriodicUpdatesEvent(newState)
12867      int newState;
12868 {
12869     if (newState == appData.periodicUpdates)
12870       return;
12871
12872     appData.periodicUpdates=newState;
12873
12874     /* Display type changes, so update it now */
12875 //    DisplayAnalysis();
12876
12877     /* Get the ball rolling again... */
12878     if (newState) {
12879         AnalysisPeriodicEvent(1);
12880         StartAnalysisClock();
12881     }
12882 }
12883
12884 void
12885 PonderNextMoveEvent(newState)
12886      int newState;
12887 {
12888     if (newState == appData.ponderNextMove) return;
12889     if (gameMode == EditPosition) EditPositionDone();
12890     if (newState) {
12891         SendToProgram("hard\n", &first);
12892         if (gameMode == TwoMachinesPlay) {
12893             SendToProgram("hard\n", &second);
12894         }
12895     } else {
12896         SendToProgram("easy\n", &first);
12897         thinkOutput[0] = NULLCHAR;
12898         if (gameMode == TwoMachinesPlay) {
12899             SendToProgram("easy\n", &second);
12900         }
12901     }
12902     appData.ponderNextMove = newState;
12903 }
12904
12905 void
12906 NewSettingEvent(option, command, value)
12907      char *command;
12908      int option, value;
12909 {
12910     char buf[MSG_SIZ];
12911
12912     if (gameMode == EditPosition) EditPositionDone();
12913     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12914     SendToProgram(buf, &first);
12915     if (gameMode == TwoMachinesPlay) {
12916         SendToProgram(buf, &second);
12917     }
12918 }
12919
12920 void
12921 ShowThinkingEvent()
12922 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12923 {
12924     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12925     int newState = appData.showThinking
12926         // [HGM] thinking: other features now need thinking output as well
12927         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12928     
12929     if (oldState == newState) return;
12930     oldState = newState;
12931     if (gameMode == EditPosition) EditPositionDone();
12932     if (oldState) {
12933         SendToProgram("post\n", &first);
12934         if (gameMode == TwoMachinesPlay) {
12935             SendToProgram("post\n", &second);
12936         }
12937     } else {
12938         SendToProgram("nopost\n", &first);
12939         thinkOutput[0] = NULLCHAR;
12940         if (gameMode == TwoMachinesPlay) {
12941             SendToProgram("nopost\n", &second);
12942         }
12943     }
12944 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12945 }
12946
12947 void
12948 AskQuestionEvent(title, question, replyPrefix, which)
12949      char *title; char *question; char *replyPrefix; char *which;
12950 {
12951   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12952   if (pr == NoProc) return;
12953   AskQuestion(title, question, replyPrefix, pr);
12954 }
12955
12956 void
12957 DisplayMove(moveNumber)
12958      int moveNumber;
12959 {
12960     char message[MSG_SIZ];
12961     char res[MSG_SIZ];
12962     char cpThinkOutput[MSG_SIZ];
12963
12964     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12965     
12966     if (moveNumber == forwardMostMove - 1 || 
12967         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12968
12969         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12970
12971         if (strchr(cpThinkOutput, '\n')) {
12972             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12973         }
12974     } else {
12975         *cpThinkOutput = NULLCHAR;
12976     }
12977
12978     /* [AS] Hide thinking from human user */
12979     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12980         *cpThinkOutput = NULLCHAR;
12981         if( thinkOutput[0] != NULLCHAR ) {
12982             int i;
12983
12984             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12985                 cpThinkOutput[i] = '.';
12986             }
12987             cpThinkOutput[i] = NULLCHAR;
12988             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12989         }
12990     }
12991
12992     if (moveNumber == forwardMostMove - 1 &&
12993         gameInfo.resultDetails != NULL) {
12994         if (gameInfo.resultDetails[0] == NULLCHAR) {
12995             sprintf(res, " %s", PGNResult(gameInfo.result));
12996         } else {
12997             sprintf(res, " {%s} %s",
12998                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12999         }
13000     } else {
13001         res[0] = NULLCHAR;
13002     }
13003
13004     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13005         DisplayMessage(res, cpThinkOutput);
13006     } else {
13007         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13008                 WhiteOnMove(moveNumber) ? " " : ".. ",
13009                 parseList[moveNumber], res);
13010         DisplayMessage(message, cpThinkOutput);
13011     }
13012 }
13013
13014 void
13015 DisplayComment(moveNumber, text)
13016      int moveNumber;
13017      char *text;
13018 {
13019     char title[MSG_SIZ];
13020     char buf[8000]; // comment can be long!
13021     int score, depth;
13022
13023     if( appData.autoDisplayComment ) {
13024         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13025             strcpy(title, "Comment");
13026         } else {
13027             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13028                     WhiteOnMove(moveNumber) ? " " : ".. ",
13029                     parseList[moveNumber]);
13030         }
13031         // [HGM] PV info: display PV info together with (or as) comment
13032         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13033             if(text == NULL) text = "";                                           
13034             score = pvInfoList[moveNumber].score;
13035             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13036                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13037             text = buf;
13038         }
13039     } else title[0] = 0;
13040
13041     if (text != NULL)
13042         CommentPopUp(title, text);
13043 }
13044
13045 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13046  * might be busy thinking or pondering.  It can be omitted if your
13047  * gnuchess is configured to stop thinking immediately on any user
13048  * input.  However, that gnuchess feature depends on the FIONREAD
13049  * ioctl, which does not work properly on some flavors of Unix.
13050  */
13051 void
13052 Attention(cps)
13053      ChessProgramState *cps;
13054 {
13055 #if ATTENTION
13056     if (!cps->useSigint) return;
13057     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13058     switch (gameMode) {
13059       case MachinePlaysWhite:
13060       case MachinePlaysBlack:
13061       case TwoMachinesPlay:
13062       case IcsPlayingWhite:
13063       case IcsPlayingBlack:
13064       case AnalyzeMode:
13065       case AnalyzeFile:
13066         /* Skip if we know it isn't thinking */
13067         if (!cps->maybeThinking) return;
13068         if (appData.debugMode)
13069           fprintf(debugFP, "Interrupting %s\n", cps->which);
13070         InterruptChildProcess(cps->pr);
13071         cps->maybeThinking = FALSE;
13072         break;
13073       default:
13074         break;
13075     }
13076 #endif /*ATTENTION*/
13077 }
13078
13079 int
13080 CheckFlags()
13081 {
13082     if (whiteTimeRemaining <= 0) {
13083         if (!whiteFlag) {
13084             whiteFlag = TRUE;
13085             if (appData.icsActive) {
13086                 if (appData.autoCallFlag &&
13087                     gameMode == IcsPlayingBlack && !blackFlag) {
13088                   SendToICS(ics_prefix);
13089                   SendToICS("flag\n");
13090                 }
13091             } else {
13092                 if (blackFlag) {
13093                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13094                 } else {
13095                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13096                     if (appData.autoCallFlag) {
13097                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13098                         return TRUE;
13099                     }
13100                 }
13101             }
13102         }
13103     }
13104     if (blackTimeRemaining <= 0) {
13105         if (!blackFlag) {
13106             blackFlag = TRUE;
13107             if (appData.icsActive) {
13108                 if (appData.autoCallFlag &&
13109                     gameMode == IcsPlayingWhite && !whiteFlag) {
13110                   SendToICS(ics_prefix);
13111                   SendToICS("flag\n");
13112                 }
13113             } else {
13114                 if (whiteFlag) {
13115                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13116                 } else {
13117                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13118                     if (appData.autoCallFlag) {
13119                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13120                         return TRUE;
13121                     }
13122                 }
13123             }
13124         }
13125     }
13126     return FALSE;
13127 }
13128
13129 void
13130 CheckTimeControl()
13131 {
13132     if (!appData.clockMode || appData.icsActive ||
13133         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13134
13135     /*
13136      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13137      */
13138     if ( !WhiteOnMove(forwardMostMove) )
13139         /* White made time control */
13140         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13141         /* [HGM] time odds: correct new time quota for time odds! */
13142                                             / WhitePlayer()->timeOdds;
13143       else
13144         /* Black made time control */
13145         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13146                                             / WhitePlayer()->other->timeOdds;
13147 }
13148
13149 void
13150 DisplayBothClocks()
13151 {
13152     int wom = gameMode == EditPosition ?
13153       !blackPlaysFirst : WhiteOnMove(currentMove);
13154     DisplayWhiteClock(whiteTimeRemaining, wom);
13155     DisplayBlackClock(blackTimeRemaining, !wom);
13156 }
13157
13158
13159 /* Timekeeping seems to be a portability nightmare.  I think everyone
13160    has ftime(), but I'm really not sure, so I'm including some ifdefs
13161    to use other calls if you don't.  Clocks will be less accurate if
13162    you have neither ftime nor gettimeofday.
13163 */
13164
13165 /* VS 2008 requires the #include outside of the function */
13166 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13167 #include <sys/timeb.h>
13168 #endif
13169
13170 /* Get the current time as a TimeMark */
13171 void
13172 GetTimeMark(tm)
13173      TimeMark *tm;
13174 {
13175 #if HAVE_GETTIMEOFDAY
13176
13177     struct timeval timeVal;
13178     struct timezone timeZone;
13179
13180     gettimeofday(&timeVal, &timeZone);
13181     tm->sec = (long) timeVal.tv_sec; 
13182     tm->ms = (int) (timeVal.tv_usec / 1000L);
13183
13184 #else /*!HAVE_GETTIMEOFDAY*/
13185 #if HAVE_FTIME
13186
13187 // include <sys/timeb.h> / moved to just above start of function
13188     struct timeb timeB;
13189
13190     ftime(&timeB);
13191     tm->sec = (long) timeB.time;
13192     tm->ms = (int) timeB.millitm;
13193
13194 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13195     tm->sec = (long) time(NULL);
13196     tm->ms = 0;
13197 #endif
13198 #endif
13199 }
13200
13201 /* Return the difference in milliseconds between two
13202    time marks.  We assume the difference will fit in a long!
13203 */
13204 long
13205 SubtractTimeMarks(tm2, tm1)
13206      TimeMark *tm2, *tm1;
13207 {
13208     return 1000L*(tm2->sec - tm1->sec) +
13209            (long) (tm2->ms - tm1->ms);
13210 }
13211
13212
13213 /*
13214  * Code to manage the game clocks.
13215  *
13216  * In tournament play, black starts the clock and then white makes a move.
13217  * We give the human user a slight advantage if he is playing white---the
13218  * clocks don't run until he makes his first move, so it takes zero time.
13219  * Also, we don't account for network lag, so we could get out of sync
13220  * with GNU Chess's clock -- but then, referees are always right.  
13221  */
13222
13223 static TimeMark tickStartTM;
13224 static long intendedTickLength;
13225
13226 long
13227 NextTickLength(timeRemaining)
13228      long timeRemaining;
13229 {
13230     long nominalTickLength, nextTickLength;
13231
13232     if (timeRemaining > 0L && timeRemaining <= 10000L)
13233       nominalTickLength = 100L;
13234     else
13235       nominalTickLength = 1000L;
13236     nextTickLength = timeRemaining % nominalTickLength;
13237     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13238
13239     return nextTickLength;
13240 }
13241
13242 /* Adjust clock one minute up or down */
13243 void
13244 AdjustClock(Boolean which, int dir)
13245 {
13246     if(which) blackTimeRemaining += 60000*dir;
13247     else      whiteTimeRemaining += 60000*dir;
13248     DisplayBothClocks();
13249 }
13250
13251 /* Stop clocks and reset to a fresh time control */
13252 void
13253 ResetClocks() 
13254 {
13255     (void) StopClockTimer();
13256     if (appData.icsActive) {
13257         whiteTimeRemaining = blackTimeRemaining = 0;
13258     } else { /* [HGM] correct new time quote for time odds */
13259         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13260         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13261     }
13262     if (whiteFlag || blackFlag) {
13263         DisplayTitle("");
13264         whiteFlag = blackFlag = FALSE;
13265     }
13266     DisplayBothClocks();
13267 }
13268
13269 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13270
13271 /* Decrement running clock by amount of time that has passed */
13272 void
13273 DecrementClocks()
13274 {
13275     long timeRemaining;
13276     long lastTickLength, fudge;
13277     TimeMark now;
13278
13279     if (!appData.clockMode) return;
13280     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13281         
13282     GetTimeMark(&now);
13283
13284     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13285
13286     /* Fudge if we woke up a little too soon */
13287     fudge = intendedTickLength - lastTickLength;
13288     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13289
13290     if (WhiteOnMove(forwardMostMove)) {
13291         if(whiteNPS >= 0) lastTickLength = 0;
13292         timeRemaining = whiteTimeRemaining -= lastTickLength;
13293         DisplayWhiteClock(whiteTimeRemaining - fudge,
13294                           WhiteOnMove(currentMove));
13295     } else {
13296         if(blackNPS >= 0) lastTickLength = 0;
13297         timeRemaining = blackTimeRemaining -= lastTickLength;
13298         DisplayBlackClock(blackTimeRemaining - fudge,
13299                           !WhiteOnMove(currentMove));
13300     }
13301
13302     if (CheckFlags()) return;
13303         
13304     tickStartTM = now;
13305     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13306     StartClockTimer(intendedTickLength);
13307
13308     /* if the time remaining has fallen below the alarm threshold, sound the
13309      * alarm. if the alarm has sounded and (due to a takeback or time control
13310      * with increment) the time remaining has increased to a level above the
13311      * threshold, reset the alarm so it can sound again. 
13312      */
13313     
13314     if (appData.icsActive && appData.icsAlarm) {
13315
13316         /* make sure we are dealing with the user's clock */
13317         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13318                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13319            )) return;
13320
13321         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13322             alarmSounded = FALSE;
13323         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13324             PlayAlarmSound();
13325             alarmSounded = TRUE;
13326         }
13327     }
13328 }
13329
13330
13331 /* A player has just moved, so stop the previously running
13332    clock and (if in clock mode) start the other one.
13333    We redisplay both clocks in case we're in ICS mode, because
13334    ICS gives us an update to both clocks after every move.
13335    Note that this routine is called *after* forwardMostMove
13336    is updated, so the last fractional tick must be subtracted
13337    from the color that is *not* on move now.
13338 */
13339 void
13340 SwitchClocks()
13341 {
13342     long lastTickLength;
13343     TimeMark now;
13344     int flagged = FALSE;
13345
13346     GetTimeMark(&now);
13347
13348     if (StopClockTimer() && appData.clockMode) {
13349         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13350         if (WhiteOnMove(forwardMostMove)) {
13351             if(blackNPS >= 0) lastTickLength = 0;
13352             blackTimeRemaining -= lastTickLength;
13353            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13354 //         if(pvInfoList[forwardMostMove-1].time == -1)
13355                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13356                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13357         } else {
13358            if(whiteNPS >= 0) lastTickLength = 0;
13359            whiteTimeRemaining -= lastTickLength;
13360            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13361 //         if(pvInfoList[forwardMostMove-1].time == -1)
13362                  pvInfoList[forwardMostMove-1].time = 
13363                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13364         }
13365         flagged = CheckFlags();
13366     }
13367     CheckTimeControl();
13368
13369     if (flagged || !appData.clockMode) return;
13370
13371     switch (gameMode) {
13372       case MachinePlaysBlack:
13373       case MachinePlaysWhite:
13374       case BeginningOfGame:
13375         if (pausing) return;
13376         break;
13377
13378       case EditGame:
13379       case PlayFromGameFile:
13380       case IcsExamining:
13381         return;
13382
13383       default:
13384         break;
13385     }
13386
13387     tickStartTM = now;
13388     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13389       whiteTimeRemaining : blackTimeRemaining);
13390     StartClockTimer(intendedTickLength);
13391 }
13392         
13393
13394 /* Stop both clocks */
13395 void
13396 StopClocks()
13397 {       
13398     long lastTickLength;
13399     TimeMark now;
13400
13401     if (!StopClockTimer()) return;
13402     if (!appData.clockMode) return;
13403
13404     GetTimeMark(&now);
13405
13406     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13407     if (WhiteOnMove(forwardMostMove)) {
13408         if(whiteNPS >= 0) lastTickLength = 0;
13409         whiteTimeRemaining -= lastTickLength;
13410         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13411     } else {
13412         if(blackNPS >= 0) lastTickLength = 0;
13413         blackTimeRemaining -= lastTickLength;
13414         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13415     }
13416     CheckFlags();
13417 }
13418         
13419 /* Start clock of player on move.  Time may have been reset, so
13420    if clock is already running, stop and restart it. */
13421 void
13422 StartClocks()
13423 {
13424     (void) StopClockTimer(); /* in case it was running already */
13425     DisplayBothClocks();
13426     if (CheckFlags()) return;
13427
13428     if (!appData.clockMode) return;
13429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13430
13431     GetTimeMark(&tickStartTM);
13432     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13433       whiteTimeRemaining : blackTimeRemaining);
13434
13435    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13436     whiteNPS = blackNPS = -1; 
13437     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13438        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13439         whiteNPS = first.nps;
13440     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13441        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13442         blackNPS = first.nps;
13443     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13444         whiteNPS = second.nps;
13445     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13446         blackNPS = second.nps;
13447     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13448
13449     StartClockTimer(intendedTickLength);
13450 }
13451
13452 char *
13453 TimeString(ms)
13454      long ms;
13455 {
13456     long second, minute, hour, day;
13457     char *sign = "";
13458     static char buf[32];
13459     
13460     if (ms > 0 && ms <= 9900) {
13461       /* convert milliseconds to tenths, rounding up */
13462       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13463
13464       sprintf(buf, " %03.1f ", tenths/10.0);
13465       return buf;
13466     }
13467
13468     /* convert milliseconds to seconds, rounding up */
13469     /* use floating point to avoid strangeness of integer division
13470        with negative dividends on many machines */
13471     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13472
13473     if (second < 0) {
13474         sign = "-";
13475         second = -second;
13476     }
13477     
13478     day = second / (60 * 60 * 24);
13479     second = second % (60 * 60 * 24);
13480     hour = second / (60 * 60);
13481     second = second % (60 * 60);
13482     minute = second / 60;
13483     second = second % 60;
13484     
13485     if (day > 0)
13486       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13487               sign, day, hour, minute, second);
13488     else if (hour > 0)
13489       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13490     else
13491       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13492     
13493     return buf;
13494 }
13495
13496
13497 /*
13498  * This is necessary because some C libraries aren't ANSI C compliant yet.
13499  */
13500 char *
13501 StrStr(string, match)
13502      char *string, *match;
13503 {
13504     int i, length;
13505     
13506     length = strlen(match);
13507     
13508     for (i = strlen(string) - length; i >= 0; i--, string++)
13509       if (!strncmp(match, string, length))
13510         return string;
13511     
13512     return NULL;
13513 }
13514
13515 char *
13516 StrCaseStr(string, match)
13517      char *string, *match;
13518 {
13519     int i, j, length;
13520     
13521     length = strlen(match);
13522     
13523     for (i = strlen(string) - length; i >= 0; i--, string++) {
13524         for (j = 0; j < length; j++) {
13525             if (ToLower(match[j]) != ToLower(string[j]))
13526               break;
13527         }
13528         if (j == length) return string;
13529     }
13530
13531     return NULL;
13532 }
13533
13534 #ifndef _amigados
13535 int
13536 StrCaseCmp(s1, s2)
13537      char *s1, *s2;
13538 {
13539     char c1, c2;
13540     
13541     for (;;) {
13542         c1 = ToLower(*s1++);
13543         c2 = ToLower(*s2++);
13544         if (c1 > c2) return 1;
13545         if (c1 < c2) return -1;
13546         if (c1 == NULLCHAR) return 0;
13547     }
13548 }
13549
13550
13551 int
13552 ToLower(c)
13553      int c;
13554 {
13555     return isupper(c) ? tolower(c) : c;
13556 }
13557
13558
13559 int
13560 ToUpper(c)
13561      int c;
13562 {
13563     return islower(c) ? toupper(c) : c;
13564 }
13565 #endif /* !_amigados    */
13566
13567 char *
13568 StrSave(s)
13569      char *s;
13570 {
13571     char *ret;
13572
13573     if ((ret = (char *) malloc(strlen(s) + 1))) {
13574         strcpy(ret, s);
13575     }
13576     return ret;
13577 }
13578
13579 char *
13580 StrSavePtr(s, savePtr)
13581      char *s, **savePtr;
13582 {
13583     if (*savePtr) {
13584         free(*savePtr);
13585     }
13586     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13587         strcpy(*savePtr, s);
13588     }
13589     return(*savePtr);
13590 }
13591
13592 char *
13593 PGNDate()
13594 {
13595     time_t clock;
13596     struct tm *tm;
13597     char buf[MSG_SIZ];
13598
13599     clock = time((time_t *)NULL);
13600     tm = localtime(&clock);
13601     sprintf(buf, "%04d.%02d.%02d",
13602             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13603     return StrSave(buf);
13604 }
13605
13606
13607 char *
13608 PositionToFEN(move, overrideCastling)
13609      int move;
13610      char *overrideCastling;
13611 {
13612     int i, j, fromX, fromY, toX, toY;
13613     int whiteToPlay;
13614     char buf[128];
13615     char *p, *q;
13616     int emptycount;
13617     ChessSquare piece;
13618
13619     whiteToPlay = (gameMode == EditPosition) ?
13620       !blackPlaysFirst : (move % 2 == 0);
13621     p = buf;
13622
13623     /* Piece placement data */
13624     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13625         emptycount = 0;
13626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13627             if (boards[move][i][j] == EmptySquare) {
13628                 emptycount++;
13629             } else { ChessSquare piece = boards[move][i][j];
13630                 if (emptycount > 0) {
13631                     if(emptycount<10) /* [HGM] can be >= 10 */
13632                         *p++ = '0' + emptycount;
13633                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13634                     emptycount = 0;
13635                 }
13636                 if(PieceToChar(piece) == '+') {
13637                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13638                     *p++ = '+';
13639                     piece = (ChessSquare)(DEMOTED piece);
13640                 } 
13641                 *p++ = PieceToChar(piece);
13642                 if(p[-1] == '~') {
13643                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13644                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13645                     *p++ = '~';
13646                 }
13647             }
13648         }
13649         if (emptycount > 0) {
13650             if(emptycount<10) /* [HGM] can be >= 10 */
13651                 *p++ = '0' + emptycount;
13652             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13653             emptycount = 0;
13654         }
13655         *p++ = '/';
13656     }
13657     *(p - 1) = ' ';
13658
13659     /* [HGM] print Crazyhouse or Shogi holdings */
13660     if( gameInfo.holdingsWidth ) {
13661         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13662         q = p;
13663         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13664             piece = boards[move][i][BOARD_WIDTH-1];
13665             if( piece != EmptySquare )
13666               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13667                   *p++ = PieceToChar(piece);
13668         }
13669         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13670             piece = boards[move][BOARD_HEIGHT-i-1][0];
13671             if( piece != EmptySquare )
13672               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13673                   *p++ = PieceToChar(piece);
13674         }
13675
13676         if( q == p ) *p++ = '-';
13677         *p++ = ']';
13678         *p++ = ' ';
13679     }
13680
13681     /* Active color */
13682     *p++ = whiteToPlay ? 'w' : 'b';
13683     *p++ = ' ';
13684
13685   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13686     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13687   } else {
13688   if(nrCastlingRights) {
13689      q = p;
13690      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13691        /* [HGM] write directly from rights */
13692            if(castlingRights[move][2] >= 0 &&
13693               castlingRights[move][0] >= 0   )
13694                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13695            if(castlingRights[move][2] >= 0 &&
13696               castlingRights[move][1] >= 0   )
13697                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13698            if(castlingRights[move][5] >= 0 &&
13699               castlingRights[move][3] >= 0   )
13700                 *p++ = castlingRights[move][3] + AAA;
13701            if(castlingRights[move][5] >= 0 &&
13702               castlingRights[move][4] >= 0   )
13703                 *p++ = castlingRights[move][4] + AAA;
13704      } else {
13705
13706         /* [HGM] write true castling rights */
13707         if( nrCastlingRights == 6 ) {
13708             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13709                castlingRights[move][2] >= 0  ) *p++ = 'K';
13710             if(castlingRights[move][1] == BOARD_LEFT &&
13711                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13712             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13713                castlingRights[move][5] >= 0  ) *p++ = 'k';
13714             if(castlingRights[move][4] == BOARD_LEFT &&
13715                castlingRights[move][5] >= 0  ) *p++ = 'q';
13716         }
13717      }
13718      if (q == p) *p++ = '-'; /* No castling rights */
13719      *p++ = ' ';
13720   }
13721
13722   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13723      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13724     /* En passant target square */
13725     if (move > backwardMostMove) {
13726         fromX = moveList[move - 1][0] - AAA;
13727         fromY = moveList[move - 1][1] - ONE;
13728         toX = moveList[move - 1][2] - AAA;
13729         toY = moveList[move - 1][3] - ONE;
13730         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13731             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13732             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13733             fromX == toX) {
13734             /* 2-square pawn move just happened */
13735             *p++ = toX + AAA;
13736             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13737         } else {
13738             *p++ = '-';
13739         }
13740     } else if(move == backwardMostMove) {
13741         // [HGM] perhaps we should always do it like this, and forget the above?
13742         if(epStatus[move] >= 0) {
13743             *p++ = epStatus[move] + AAA;
13744             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13745         } else {
13746             *p++ = '-';
13747         }
13748     } else {
13749         *p++ = '-';
13750     }
13751     *p++ = ' ';
13752   }
13753   }
13754
13755     /* [HGM] find reversible plies */
13756     {   int i = 0, j=move;
13757
13758         if (appData.debugMode) { int k;
13759             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13760             for(k=backwardMostMove; k<=forwardMostMove; k++)
13761                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13762
13763         }
13764
13765         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13766         if( j == backwardMostMove ) i += initialRulePlies;
13767         sprintf(p, "%d ", i);
13768         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13769     }
13770     /* Fullmove number */
13771     sprintf(p, "%d", (move / 2) + 1);
13772     
13773     return StrSave(buf);
13774 }
13775
13776 Boolean
13777 ParseFEN(board, blackPlaysFirst, fen)
13778     Board board;
13779      int *blackPlaysFirst;
13780      char *fen;
13781 {
13782     int i, j;
13783     char *p;
13784     int emptycount;
13785     ChessSquare piece;
13786
13787     p = fen;
13788
13789     /* [HGM] by default clear Crazyhouse holdings, if present */
13790     if(gameInfo.holdingsWidth) {
13791        for(i=0; i<BOARD_HEIGHT; i++) {
13792            board[i][0]             = EmptySquare; /* black holdings */
13793            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13794            board[i][1]             = (ChessSquare) 0; /* black counts */
13795            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13796        }
13797     }
13798
13799     /* Piece placement data */
13800     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13801         j = 0;
13802         for (;;) {
13803             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13804                 if (*p == '/') p++;
13805                 emptycount = gameInfo.boardWidth - j;
13806                 while (emptycount--)
13807                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13808                 break;
13809 #if(BOARD_SIZE >= 10)
13810             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13811                 p++; emptycount=10;
13812                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13813                 while (emptycount--)
13814                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13815 #endif
13816             } else if (isdigit(*p)) {
13817                 emptycount = *p++ - '0';
13818                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13819                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13820                 while (emptycount--)
13821                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13822             } else if (*p == '+' || isalpha(*p)) {
13823                 if (j >= gameInfo.boardWidth) return FALSE;
13824                 if(*p=='+') {
13825                     piece = CharToPiece(*++p);
13826                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13827                     piece = (ChessSquare) (PROMOTED piece ); p++;
13828                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13829                 } else piece = CharToPiece(*p++);
13830
13831                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13832                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13833                     piece = (ChessSquare) (PROMOTED piece);
13834                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13835                     p++;
13836                 }
13837                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13838             } else {
13839                 return FALSE;
13840             }
13841         }
13842     }
13843     while (*p == '/' || *p == ' ') p++;
13844
13845     /* [HGM] look for Crazyhouse holdings here */
13846     while(*p==' ') p++;
13847     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13848         if(*p == '[') p++;
13849         if(*p == '-' ) *p++; /* empty holdings */ else {
13850             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13851             /* if we would allow FEN reading to set board size, we would   */
13852             /* have to add holdings and shift the board read so far here   */
13853             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13854                 *p++;
13855                 if((int) piece >= (int) BlackPawn ) {
13856                     i = (int)piece - (int)BlackPawn;
13857                     i = PieceToNumber((ChessSquare)i);
13858                     if( i >= gameInfo.holdingsSize ) return FALSE;
13859                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13860                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13861                 } else {
13862                     i = (int)piece - (int)WhitePawn;
13863                     i = PieceToNumber((ChessSquare)i);
13864                     if( i >= gameInfo.holdingsSize ) return FALSE;
13865                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13866                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13867                 }
13868             }
13869         }
13870         if(*p == ']') *p++;
13871     }
13872
13873     while(*p == ' ') p++;
13874
13875     /* Active color */
13876     switch (*p++) {
13877       case 'w':
13878         *blackPlaysFirst = FALSE;
13879         break;
13880       case 'b': 
13881         *blackPlaysFirst = TRUE;
13882         break;
13883       default:
13884         return FALSE;
13885     }
13886
13887     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13888     /* return the extra info in global variiables             */
13889
13890     /* set defaults in case FEN is incomplete */
13891     FENepStatus = EP_UNKNOWN;
13892     for(i=0; i<nrCastlingRights; i++ ) {
13893         FENcastlingRights[i] =
13894             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13895     }   /* assume possible unless obviously impossible */
13896     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13897     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13898     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13899     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13900     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13901     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13902     FENrulePlies = 0;
13903
13904     while(*p==' ') p++;
13905     if(nrCastlingRights) {
13906       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13907           /* castling indicator present, so default becomes no castlings */
13908           for(i=0; i<nrCastlingRights; i++ ) {
13909                  FENcastlingRights[i] = -1;
13910           }
13911       }
13912       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13913              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13914              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13915              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13916         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13917
13918         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13919             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13920             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13921         }
13922         switch(c) {
13923           case'K':
13924               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13925               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13926               FENcastlingRights[2] = whiteKingFile;
13927               break;
13928           case'Q':
13929               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13930               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13931               FENcastlingRights[2] = whiteKingFile;
13932               break;
13933           case'k':
13934               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13935               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13936               FENcastlingRights[5] = blackKingFile;
13937               break;
13938           case'q':
13939               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13940               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13941               FENcastlingRights[5] = blackKingFile;
13942           case '-':
13943               break;
13944           default: /* FRC castlings */
13945               if(c >= 'a') { /* black rights */
13946                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13947                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13948                   if(i == BOARD_RGHT) break;
13949                   FENcastlingRights[5] = i;
13950                   c -= AAA;
13951                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13952                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13953                   if(c > i)
13954                       FENcastlingRights[3] = c;
13955                   else
13956                       FENcastlingRights[4] = c;
13957               } else { /* white rights */
13958                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13959                     if(board[0][i] == WhiteKing) break;
13960                   if(i == BOARD_RGHT) break;
13961                   FENcastlingRights[2] = i;
13962                   c -= AAA - 'a' + 'A';
13963                   if(board[0][c] >= WhiteKing) break;
13964                   if(c > i)
13965                       FENcastlingRights[0] = c;
13966                   else
13967                       FENcastlingRights[1] = c;
13968               }
13969         }
13970       }
13971     if (appData.debugMode) {
13972         fprintf(debugFP, "FEN castling rights:");
13973         for(i=0; i<nrCastlingRights; i++)
13974         fprintf(debugFP, " %d", FENcastlingRights[i]);
13975         fprintf(debugFP, "\n");
13976     }
13977
13978       while(*p==' ') p++;
13979     }
13980
13981     /* read e.p. field in games that know e.p. capture */
13982     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13983        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13984       if(*p=='-') {
13985         p++; FENepStatus = EP_NONE;
13986       } else {
13987          char c = *p++ - AAA;
13988
13989          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13990          if(*p >= '0' && *p <='9') *p++;
13991          FENepStatus = c;
13992       }
13993     }
13994
13995
13996     if(sscanf(p, "%d", &i) == 1) {
13997         FENrulePlies = i; /* 50-move ply counter */
13998         /* (The move number is still ignored)    */
13999     }
14000
14001     return TRUE;
14002 }
14003       
14004 void
14005 EditPositionPasteFEN(char *fen)
14006 {
14007   if (fen != NULL) {
14008     Board initial_position;
14009
14010     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14011       DisplayError(_("Bad FEN position in clipboard"), 0);
14012       return ;
14013     } else {
14014       int savedBlackPlaysFirst = blackPlaysFirst;
14015       EditPositionEvent();
14016       blackPlaysFirst = savedBlackPlaysFirst;
14017       CopyBoard(boards[0], initial_position);
14018           /* [HGM] copy FEN attributes as well */
14019           {   int i;
14020               initialRulePlies = FENrulePlies;
14021               epStatus[0] = FENepStatus;
14022               for( i=0; i<nrCastlingRights; i++ )
14023                   castlingRights[0][i] = FENcastlingRights[i];
14024           }
14025       EditPositionDone();
14026       DisplayBothClocks();
14027       DrawPosition(FALSE, boards[currentMove]);
14028     }
14029   }
14030 }
14031
14032 static char cseq[12] = "\\   ";
14033
14034 Boolean set_cont_sequence(char *new_seq)
14035 {
14036     int len;
14037     Boolean ret;
14038
14039     // handle bad attempts to set the sequence
14040         if (!new_seq)
14041                 return 0; // acceptable error - no debug
14042
14043     len = strlen(new_seq);
14044     ret = (len > 0) && (len < sizeof(cseq));
14045     if (ret)
14046         strcpy(cseq, new_seq);
14047     else if (appData.debugMode)
14048         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14049     return ret;
14050 }
14051
14052 /*
14053     reformat a source message so words don't cross the width boundary.  internal
14054     newlines are not removed.  returns the wrapped size (no null character unless
14055     included in source message).  If dest is NULL, only calculate the size required
14056     for the dest buffer.  lp argument indicats line position upon entry, and it's
14057     passed back upon exit.
14058 */
14059 int wrap(char *dest, char *src, int count, int width, int *lp)
14060 {
14061     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14062
14063     cseq_len = strlen(cseq);
14064     old_line = line = *lp;
14065     ansi = len = clen = 0;
14066
14067     for (i=0; i < count; i++)
14068     {
14069         if (src[i] == '\033')
14070             ansi = 1;
14071
14072         // if we hit the width, back up
14073         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14074         {
14075             // store i & len in case the word is too long
14076             old_i = i, old_len = len;
14077
14078             // find the end of the last word
14079             while (i && src[i] != ' ' && src[i] != '\n')
14080             {
14081                 i--;
14082                 len--;
14083             }
14084
14085             // word too long?  restore i & len before splitting it
14086             if ((old_i-i+clen) >= width)
14087             {
14088                 i = old_i;
14089                 len = old_len;
14090             }
14091
14092             // extra space?
14093             if (i && src[i-1] == ' ')
14094                 len--;
14095
14096             if (src[i] != ' ' && src[i] != '\n')
14097             {
14098                 i--;
14099                 if (len)
14100                     len--;
14101             }
14102
14103             // now append the newline and continuation sequence
14104             if (dest)
14105                 dest[len] = '\n';
14106             len++;
14107             if (dest)
14108                 strncpy(dest+len, cseq, cseq_len);
14109             len += cseq_len;
14110             line = cseq_len;
14111             clen = cseq_len;
14112             continue;
14113         }
14114
14115         if (dest)
14116             dest[len] = src[i];
14117         len++;
14118         if (!ansi)
14119             line++;
14120         if (src[i] == '\n')
14121             line = 0;
14122         if (src[i] == 'm')
14123             ansi = 0;
14124     }
14125     if (dest && appData.debugMode)
14126     {
14127         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14128             count, width, line, len, *lp);
14129         show_bytes(debugFP, src, count);
14130         fprintf(debugFP, "\ndest: ");
14131         show_bytes(debugFP, dest, len);
14132         fprintf(debugFP, "\n");
14133     }
14134     *lp = dest ? line : old_line;
14135
14136     return len;
14137 }