c3178c41f6702ad7fe20ad30a686ba57491f9e72
[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
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        break;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(TRUE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(TRUE); }
2016 }
2017
2018 static int loggedOn = FALSE;
2019
2020 /*-- Game start info cache: --*/
2021 int gs_gamenum;
2022 char gs_kind[MSG_SIZ];
2023 static char player1Name[128] = "";
2024 static char player2Name[128] = "";
2025 static char cont_seq[] = "\n\\   ";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2029
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2032
2033 void
2034 read_from_ics(isr, closure, data, count, error)
2035      InputSourceRef isr;
2036      VOIDSTAR closure;
2037      char *data;
2038      int count;
2039      int error;
2040 {
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2050     
2051     static int started = STARTED_NONE;
2052     static char parse[20000];
2053     static int parse_pos = 0;
2054     static char buf[BUF_SIZE + 1];
2055     static int firstTime = TRUE, intfSet = FALSE;
2056     static ColorClass prevColor = ColorNormal;
2057     static int savingComment = FALSE;
2058     static int cmatch = 0; // continuation sequence match
2059     char *bp;
2060     char str[500];
2061     int i, oldi;
2062     int buf_len;
2063     int next_out;
2064     int tkind;
2065     int backup;    /* [DM] For zippy color lines */
2066     char *p;
2067     char talker[MSG_SIZ]; // [HGM] chat
2068     int channel;
2069
2070     if (appData.debugMode) {
2071       if (!error) {
2072         fprintf(debugFP, "<ICS: ");
2073         show_bytes(debugFP, data, count);
2074         fprintf(debugFP, "\n");
2075       }
2076     }
2077
2078     if (appData.debugMode) { int f = forwardMostMove;
2079         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2080                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2081     }
2082     if (count > 0) {
2083         /* If last read ended with a partial line that we couldn't parse,
2084            prepend it to the new read and try again. */
2085         if (leftover_len > 0) {
2086             for (i=0; i<leftover_len; i++)
2087               buf[i] = buf[leftover_start + i];
2088         }
2089
2090     /* copy new characters into the buffer */
2091     bp = buf + leftover_len;
2092     buf_len=leftover_len;
2093     for (i=0; i<count; i++)
2094     {
2095         // ignore these
2096         if (data[i] == '\r')
2097             continue;
2098
2099         // join lines split by ICS?
2100         if (!appData.noJoin)
2101         {
2102             /*
2103                 Joining just consists of finding matches against the
2104                 continuation sequence, and discarding that sequence
2105                 if found instead of copying it.  So, until a match
2106                 fails, there's nothing to do since it might be the
2107                 complete sequence, and thus, something we don't want
2108                 copied.
2109             */
2110             if (data[i] == cont_seq[cmatch])
2111             {
2112                 cmatch++;
2113                 if (cmatch == strlen(cont_seq))
2114                 {
2115                     cmatch = 0; // complete match.  just reset the counter
2116
2117                     /*
2118                         it's possible for the ICS to not include the space
2119                         at the end of the last word, making our [correct]
2120                         join operation fuse two separate words.  the server
2121                         does this when the space occurs at the width setting.
2122                     */
2123                     if (!buf_len || buf[buf_len-1] != ' ')
2124                     {
2125                         *bp++ = ' ';
2126                         buf_len++;
2127                     }
2128                 }
2129                 continue;
2130             }
2131             else if (cmatch)
2132             {
2133                 /*
2134                     match failed, so we have to copy what matched before
2135                     falling through and copying this character.  In reality,
2136                     this will only ever be just the newline character, but
2137                     it doesn't hurt to be precise.
2138                 */
2139                 strncpy(bp, cont_seq, cmatch);
2140                 bp += cmatch;
2141                 buf_len += cmatch;
2142                 cmatch = 0;
2143             }
2144         }
2145
2146         // copy this char
2147         *bp++ = data[i];
2148         buf_len++;
2149     }
2150
2151         buf[buf_len] = NULLCHAR;
2152         next_out = leftover_len;
2153         leftover_start = 0;
2154         
2155         i = 0;
2156         while (i < buf_len) {
2157             /* Deal with part of the TELNET option negotiation
2158                protocol.  We refuse to do anything beyond the
2159                defaults, except that we allow the WILL ECHO option,
2160                which ICS uses to turn off password echoing when we are
2161                directly connected to it.  We reject this option
2162                if localLineEditing mode is on (always on in xboard)
2163                and we are talking to port 23, which might be a real
2164                telnet server that will try to keep WILL ECHO on permanently.
2165              */
2166             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2167                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2168                 unsigned char option;
2169                 oldi = i;
2170                 switch ((unsigned char) buf[++i]) {
2171                   case TN_WILL:
2172                     if (appData.debugMode)
2173                       fprintf(debugFP, "\n<WILL ");
2174                     switch (option = (unsigned char) buf[++i]) {
2175                       case TN_ECHO:
2176                         if (appData.debugMode)
2177                           fprintf(debugFP, "ECHO ");
2178                         /* Reply only if this is a change, according
2179                            to the protocol rules. */
2180                         if (remoteEchoOption) break;
2181                         if (appData.localLineEditing &&
2182                             atoi(appData.icsPort) == TN_PORT) {
2183                             TelnetRequest(TN_DONT, TN_ECHO);
2184                         } else {
2185                             EchoOff();
2186                             TelnetRequest(TN_DO, TN_ECHO);
2187                             remoteEchoOption = TRUE;
2188                         }
2189                         break;
2190                       default:
2191                         if (appData.debugMode)
2192                           fprintf(debugFP, "%d ", option);
2193                         /* Whatever this is, we don't want it. */
2194                         TelnetRequest(TN_DONT, option);
2195                         break;
2196                     }
2197                     break;
2198                   case TN_WONT:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<WONT ");
2201                     switch (option = (unsigned char) buf[++i]) {
2202                       case TN_ECHO:
2203                         if (appData.debugMode)
2204                           fprintf(debugFP, "ECHO ");
2205                         /* Reply only if this is a change, according
2206                            to the protocol rules. */
2207                         if (!remoteEchoOption) break;
2208                         EchoOn();
2209                         TelnetRequest(TN_DONT, TN_ECHO);
2210                         remoteEchoOption = FALSE;
2211                         break;
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", (unsigned char) option);
2215                         /* Whatever this is, it must already be turned
2216                            off, because we never agree to turn on
2217                            anything non-default, so according to the
2218                            protocol rules, we don't reply. */
2219                         break;
2220                     }
2221                     break;
2222                   case TN_DO:
2223                     if (appData.debugMode)
2224                       fprintf(debugFP, "\n<DO ");
2225                     switch (option = (unsigned char) buf[++i]) {
2226                       default:
2227                         /* Whatever this is, we refuse to do it. */
2228                         if (appData.debugMode)
2229                           fprintf(debugFP, "%d ", option);
2230                         TelnetRequest(TN_WONT, option);
2231                         break;
2232                     }
2233                     break;
2234                   case TN_DONT:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<DONT ");
2237                     switch (option = (unsigned char) buf[++i]) {
2238                       default:
2239                         if (appData.debugMode)
2240                           fprintf(debugFP, "%d ", option);
2241                         /* Whatever this is, we are already not doing
2242                            it, because we never agree to do anything
2243                            non-default, so according to the protocol
2244                            rules, we don't reply. */
2245                         break;
2246                     }
2247                     break;
2248                   case TN_IAC:
2249                     if (appData.debugMode)
2250                       fprintf(debugFP, "\n<IAC ");
2251                     /* Doubled IAC; pass it through */
2252                     i--;
2253                     break;
2254                   default:
2255                     if (appData.debugMode)
2256                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2257                     /* Drop all other telnet commands on the floor */
2258                     break;
2259                 }
2260                 if (oldi > next_out)
2261                   SendToPlayer(&buf[next_out], oldi - next_out);
2262                 if (++i > next_out)
2263                   next_out = i;
2264                 continue;
2265             }
2266                 
2267             /* OK, this at least will *usually* work */
2268             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2269                 loggedOn = TRUE;
2270             }
2271             
2272             if (loggedOn && !intfSet) {
2273                 if (ics_type == ICS_ICC) {
2274                   sprintf(str,
2275                           "/set-quietly interface %s\n/set-quietly style 12\n",
2276                           programVersion);
2277                 } else if (ics_type == ICS_CHESSNET) {
2278                   sprintf(str, "/style 12\n");
2279                 } else {
2280                   strcpy(str, "alias $ @\n$set interface ");
2281                   strcat(str, programVersion);
2282                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2283 #ifdef WIN32
2284                   strcat(str, "$iset nohighlight 1\n");
2285 #endif
2286                   strcat(str, "$iset lock 1\n$style 12\n");
2287                 }
2288                 SendToICS(str);
2289                 NotifyFrontendLogin();
2290                 intfSet = TRUE;
2291             }
2292
2293             if (started == STARTED_COMMENT) {
2294                 /* Accumulate characters in comment */
2295                 parse[parse_pos++] = buf[i];
2296                 if (buf[i] == '\n') {
2297                     parse[parse_pos] = NULLCHAR;
2298                     if(chattingPartner>=0) {
2299                         char mess[MSG_SIZ];
2300                         sprintf(mess, "%s%s", talker, parse);
2301                         OutputChatMessage(chattingPartner, mess);
2302                         chattingPartner = -1;
2303                     } else
2304                     if(!suppressKibitz) // [HGM] kibitz
2305                         AppendComment(forwardMostMove, StripHighlight(parse));
2306                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2307                         int nrDigit = 0, nrAlph = 0, i;
2308                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2309                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2310                         parse[parse_pos] = NULLCHAR;
2311                         // try to be smart: if it does not look like search info, it should go to
2312                         // ICS interaction window after all, not to engine-output window.
2313                         for(i=0; i<parse_pos; i++) { // count letters and digits
2314                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2315                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2316                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2317                         }
2318                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2319                             int depth=0; float score;
2320                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2321                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2322                                 pvInfoList[forwardMostMove-1].depth = depth;
2323                                 pvInfoList[forwardMostMove-1].score = 100*score;
2324                             }
2325                             OutputKibitz(suppressKibitz, parse);
2326                         } else {
2327                             char tmp[MSG_SIZ];
2328                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2329                             SendToPlayer(tmp, strlen(tmp));
2330                         }
2331                     }
2332                     started = STARTED_NONE;
2333                 } else {
2334                     /* Don't match patterns against characters in chatter */
2335                     i++;
2336                     continue;
2337                 }
2338             }
2339             if (started == STARTED_CHATTER) {
2340                 if (buf[i] != '\n') {
2341                     /* Don't match patterns against characters in chatter */
2342                     i++;
2343                     continue;
2344                 }
2345                 started = STARTED_NONE;
2346             }
2347
2348             /* Kludge to deal with rcmd protocol */
2349             if (firstTime && looking_at(buf, &i, "\001*")) {
2350                 DisplayFatalError(&buf[1], 0, 1);
2351                 continue;
2352             } else {
2353                 firstTime = FALSE;
2354             }
2355
2356             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2357                 ics_type = ICS_ICC;
2358                 ics_prefix = "/";
2359                 if (appData.debugMode)
2360                   fprintf(debugFP, "ics_type %d\n", ics_type);
2361                 continue;
2362             }
2363             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2364                 ics_type = ICS_FICS;
2365                 ics_prefix = "$";
2366                 if (appData.debugMode)
2367                   fprintf(debugFP, "ics_type %d\n", ics_type);
2368                 continue;
2369             }
2370             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2371                 ics_type = ICS_CHESSNET;
2372                 ics_prefix = "/";
2373                 if (appData.debugMode)
2374                   fprintf(debugFP, "ics_type %d\n", ics_type);
2375                 continue;
2376             }
2377
2378             if (!loggedOn &&
2379                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2380                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2381                  looking_at(buf, &i, "will be \"*\""))) {
2382               strcpy(ics_handle, star_match[0]);
2383               continue;
2384             }
2385
2386             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2387               char buf[MSG_SIZ];
2388               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2389               DisplayIcsInteractionTitle(buf);
2390               have_set_title = TRUE;
2391             }
2392
2393             /* skip finger notes */
2394             if (started == STARTED_NONE &&
2395                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2396                  (buf[i] == '1' && buf[i+1] == '0')) &&
2397                 buf[i+2] == ':' && buf[i+3] == ' ') {
2398               started = STARTED_CHATTER;
2399               i += 3;
2400               continue;
2401             }
2402
2403             /* skip formula vars */
2404             if (started == STARTED_NONE &&
2405                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2406               started = STARTED_CHATTER;
2407               i += 3;
2408               continue;
2409             }
2410
2411             oldi = i;
2412             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2413             if (appData.autoKibitz && started == STARTED_NONE && 
2414                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2415                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2416                 if(looking_at(buf, &i, "* kibitzes: ") &&
2417                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2418                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2419                         suppressKibitz = TRUE;
2420                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2421                                 && (gameMode == IcsPlayingWhite)) ||
2422                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2423                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2424                             started = STARTED_CHATTER; // own kibitz we simply discard
2425                         else {
2426                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2427                             parse_pos = 0; parse[0] = NULLCHAR;
2428                             savingComment = TRUE;
2429                             suppressKibitz = gameMode != IcsObserving ? 2 :
2430                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2431                         } 
2432                         continue;
2433                 } else
2434                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2435                     started = STARTED_CHATTER;
2436                     suppressKibitz = TRUE;
2437                 }
2438             } // [HGM] kibitz: end of patch
2439
2440 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2441
2442             // [HGM] chat: intercept tells by users for which we have an open chat window
2443             channel = -1;
2444             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2445                                            looking_at(buf, &i, "* whispers:") ||
2446                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2447                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2448                 int p;
2449                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2450                 chattingPartner = -1;
2451
2452                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2453                 for(p=0; p<MAX_CHAT; p++) {
2454                     if(channel == atoi(chatPartner[p])) {
2455                     talker[0] = '['; strcat(talker, "]");
2456                     chattingPartner = p; break;
2457                     }
2458                 } else
2459                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2460                 for(p=0; p<MAX_CHAT; p++) {
2461                     if(!strcmp("WHISPER", chatPartner[p])) {
2462                         talker[0] = '['; strcat(talker, "]");
2463                         chattingPartner = p; break;
2464                     }
2465                 }
2466                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2467                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2468                     talker[0] = 0;
2469                     chattingPartner = p; break;
2470                 }
2471                 if(chattingPartner<0) i = oldi; else {
2472                     started = STARTED_COMMENT;
2473                     parse_pos = 0; parse[0] = NULLCHAR;
2474                     savingComment = TRUE;
2475                     suppressKibitz = TRUE;
2476                 }
2477             } // [HGM] chat: end of patch
2478
2479             if (appData.zippyTalk || appData.zippyPlay) {
2480                 /* [DM] Backup address for color zippy lines */
2481                 backup = i;
2482 #if ZIPPY
2483        #ifdef WIN32
2484                if (loggedOn == TRUE)
2485                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2486                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2487        #else
2488                 if (ZippyControl(buf, &i) ||
2489                     ZippyConverse(buf, &i) ||
2490                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2491                       loggedOn = TRUE;
2492                       if (!appData.colorize) continue;
2493                 }
2494        #endif
2495 #endif
2496             } // [DM] 'else { ' deleted
2497                 if (
2498                     /* Regular tells and says */
2499                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2500                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2501                     looking_at(buf, &i, "* says: ") ||
2502                     /* Don't color "message" or "messages" output */
2503                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2504                     looking_at(buf, &i, "*. * at *:*: ") ||
2505                     looking_at(buf, &i, "--* (*:*): ") ||
2506                     /* Message notifications (same color as tells) */
2507                     looking_at(buf, &i, "* has left a message ") ||
2508                     looking_at(buf, &i, "* just sent you a message:\n") ||
2509                     /* Whispers and kibitzes */
2510                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2511                     looking_at(buf, &i, "* kibitzes: ") ||
2512                     /* Channel tells */
2513                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2514
2515                   if (tkind == 1 && strchr(star_match[0], ':')) {
2516                       /* Avoid "tells you:" spoofs in channels */
2517                      tkind = 3;
2518                   }
2519                   if (star_match[0][0] == NULLCHAR ||
2520                       strchr(star_match[0], ' ') ||
2521                       (tkind == 3 && strchr(star_match[1], ' '))) {
2522                     /* Reject bogus matches */
2523                     i = oldi;
2524                   } else {
2525                     if (appData.colorize) {
2526                       if (oldi > next_out) {
2527                         SendToPlayer(&buf[next_out], oldi - next_out);
2528                         next_out = oldi;
2529                       }
2530                       switch (tkind) {
2531                       case 1:
2532                         Colorize(ColorTell, FALSE);
2533                         curColor = ColorTell;
2534                         break;
2535                       case 2:
2536                         Colorize(ColorKibitz, FALSE);
2537                         curColor = ColorKibitz;
2538                         break;
2539                       case 3:
2540                         p = strrchr(star_match[1], '(');
2541                         if (p == NULL) {
2542                           p = star_match[1];
2543                         } else {
2544                           p++;
2545                         }
2546                         if (atoi(p) == 1) {
2547                           Colorize(ColorChannel1, FALSE);
2548                           curColor = ColorChannel1;
2549                         } else {
2550                           Colorize(ColorChannel, FALSE);
2551                           curColor = ColorChannel;
2552                         }
2553                         break;
2554                       case 5:
2555                         curColor = ColorNormal;
2556                         break;
2557                       }
2558                     }
2559                     if (started == STARTED_NONE && appData.autoComment &&
2560                         (gameMode == IcsObserving ||
2561                          gameMode == IcsPlayingWhite ||
2562                          gameMode == IcsPlayingBlack)) {
2563                       parse_pos = i - oldi;
2564                       memcpy(parse, &buf[oldi], parse_pos);
2565                       parse[parse_pos] = NULLCHAR;
2566                       started = STARTED_COMMENT;
2567                       savingComment = TRUE;
2568                     } else {
2569                       started = STARTED_CHATTER;
2570                       savingComment = FALSE;
2571                     }
2572                     loggedOn = TRUE;
2573                     continue;
2574                   }
2575                 }
2576
2577                 if (looking_at(buf, &i, "* s-shouts: ") ||
2578                     looking_at(buf, &i, "* c-shouts: ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorSShout, FALSE);
2585                         curColor = ColorSShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at(buf, &i, "--->")) {
2593                     loggedOn = TRUE;
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "* shouts: ") ||
2598                     looking_at(buf, &i, "--> ")) {
2599                     if (appData.colorize) {
2600                         if (oldi > next_out) {
2601                             SendToPlayer(&buf[next_out], oldi - next_out);
2602                             next_out = oldi;
2603                         }
2604                         Colorize(ColorShout, FALSE);
2605                         curColor = ColorShout;
2606                     }
2607                     loggedOn = TRUE;
2608                     started = STARTED_CHATTER;
2609                     continue;
2610                 }
2611
2612                 if (looking_at( buf, &i, "Challenge:")) {
2613                     if (appData.colorize) {
2614                         if (oldi > next_out) {
2615                             SendToPlayer(&buf[next_out], oldi - next_out);
2616                             next_out = oldi;
2617                         }
2618                         Colorize(ColorChallenge, FALSE);
2619                         curColor = ColorChallenge;
2620                     }
2621                     loggedOn = TRUE;
2622                     continue;
2623                 }
2624
2625                 if (looking_at(buf, &i, "* offers you") ||
2626                     looking_at(buf, &i, "* offers to be") ||
2627                     looking_at(buf, &i, "* would like to") ||
2628                     looking_at(buf, &i, "* requests to") ||
2629                     looking_at(buf, &i, "Your opponent offers") ||
2630                     looking_at(buf, &i, "Your opponent requests")) {
2631
2632                     if (appData.colorize) {
2633                         if (oldi > next_out) {
2634                             SendToPlayer(&buf[next_out], oldi - next_out);
2635                             next_out = oldi;
2636                         }
2637                         Colorize(ColorRequest, FALSE);
2638                         curColor = ColorRequest;
2639                     }
2640                     continue;
2641                 }
2642
2643                 if (looking_at(buf, &i, "* (*) seeking")) {
2644                     if (appData.colorize) {
2645                         if (oldi > next_out) {
2646                             SendToPlayer(&buf[next_out], oldi - next_out);
2647                             next_out = oldi;
2648                         }
2649                         Colorize(ColorSeek, FALSE);
2650                         curColor = ColorSeek;
2651                     }
2652                     continue;
2653             }
2654
2655             if (looking_at(buf, &i, "\\   ")) {
2656                 if (prevColor != ColorNormal) {
2657                     if (oldi > next_out) {
2658                         SendToPlayer(&buf[next_out], oldi - next_out);
2659                         next_out = oldi;
2660                     }
2661                     Colorize(prevColor, TRUE);
2662                     curColor = prevColor;
2663                 }
2664                 if (savingComment) {
2665                     parse_pos = i - oldi;
2666                     memcpy(parse, &buf[oldi], parse_pos);
2667                     parse[parse_pos] = NULLCHAR;
2668                     started = STARTED_COMMENT;
2669                 } else {
2670                     started = STARTED_CHATTER;
2671                 }
2672                 continue;
2673             }
2674
2675             if (looking_at(buf, &i, "Black Strength :") ||
2676                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2677                 looking_at(buf, &i, "<10>") ||
2678                 looking_at(buf, &i, "#@#")) {
2679                 /* Wrong board style */
2680                 loggedOn = TRUE;
2681                 SendToICS(ics_prefix);
2682                 SendToICS("set style 12\n");
2683                 SendToICS(ics_prefix);
2684                 SendToICS("refresh\n");
2685                 continue;
2686             }
2687             
2688             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2689                 ICSInitScript();
2690                 have_sent_ICS_logon = 1;
2691                 continue;
2692             }
2693               
2694             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2695                 (looking_at(buf, &i, "\n<12> ") ||
2696                  looking_at(buf, &i, "<12> "))) {
2697                 loggedOn = TRUE;
2698                 if (oldi > next_out) {
2699                     SendToPlayer(&buf[next_out], oldi - next_out);
2700                 }
2701                 next_out = i;
2702                 started = STARTED_BOARD;
2703                 parse_pos = 0;
2704                 continue;
2705             }
2706
2707             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2708                 looking_at(buf, &i, "<b1> ")) {
2709                 if (oldi > next_out) {
2710                     SendToPlayer(&buf[next_out], oldi - next_out);
2711                 }
2712                 next_out = i;
2713                 started = STARTED_HOLDINGS;
2714                 parse_pos = 0;
2715                 continue;
2716             }
2717
2718             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2719                 loggedOn = TRUE;
2720                 /* Header for a move list -- first line */
2721
2722                 switch (ics_getting_history) {
2723                   case H_FALSE:
2724                     switch (gameMode) {
2725                       case IcsIdle:
2726                       case BeginningOfGame:
2727                         /* User typed "moves" or "oldmoves" while we
2728                            were idle.  Pretend we asked for these
2729                            moves and soak them up so user can step
2730                            through them and/or save them.
2731                            */
2732                         Reset(TRUE, TRUE);
2733                         gameMode = IcsObserving;
2734                         ModeHighlight();
2735                         ics_gamenum = -1;
2736                         ics_getting_history = H_GOT_UNREQ_HEADER;
2737                         break;
2738                       case EditGame: /*?*/
2739                       case EditPosition: /*?*/
2740                         /* Should above feature work in these modes too? */
2741                         /* For now it doesn't */
2742                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2743                         break;
2744                       default:
2745                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2746                         break;
2747                     }
2748                     break;
2749                   case H_REQUESTED:
2750                     /* Is this the right one? */
2751                     if (gameInfo.white && gameInfo.black &&
2752                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2753                         strcmp(gameInfo.black, star_match[2]) == 0) {
2754                         /* All is well */
2755                         ics_getting_history = H_GOT_REQ_HEADER;
2756                     }
2757                     break;
2758                   case H_GOT_REQ_HEADER:
2759                   case H_GOT_UNREQ_HEADER:
2760                   case H_GOT_UNWANTED_HEADER:
2761                   case H_GETTING_MOVES:
2762                     /* Should not happen */
2763                     DisplayError(_("Error gathering move list: two headers"), 0);
2764                     ics_getting_history = H_FALSE;
2765                     break;
2766                 }
2767
2768                 /* Save player ratings into gameInfo if needed */
2769                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2770                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2771                     (gameInfo.whiteRating == -1 ||
2772                      gameInfo.blackRating == -1)) {
2773
2774                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2775                     gameInfo.blackRating = string_to_rating(star_match[3]);
2776                     if (appData.debugMode)
2777                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2778                               gameInfo.whiteRating, gameInfo.blackRating);
2779                 }
2780                 continue;
2781             }
2782
2783             if (looking_at(buf, &i,
2784               "* * match, initial time: * minute*, increment: * second")) {
2785                 /* Header for a move list -- second line */
2786                 /* Initial board will follow if this is a wild game */
2787                 if (gameInfo.event != NULL) free(gameInfo.event);
2788                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2789                 gameInfo.event = StrSave(str);
2790                 /* [HGM] we switched variant. Translate boards if needed. */
2791                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2792                 continue;
2793             }
2794
2795             if (looking_at(buf, &i, "Move  ")) {
2796                 /* Beginning of a move list */
2797                 switch (ics_getting_history) {
2798                   case H_FALSE:
2799                     /* Normally should not happen */
2800                     /* Maybe user hit reset while we were parsing */
2801                     break;
2802                   case H_REQUESTED:
2803                     /* Happens if we are ignoring a move list that is not
2804                      * the one we just requested.  Common if the user
2805                      * tries to observe two games without turning off
2806                      * getMoveList */
2807                     break;
2808                   case H_GETTING_MOVES:
2809                     /* Should not happen */
2810                     DisplayError(_("Error gathering move list: nested"), 0);
2811                     ics_getting_history = H_FALSE;
2812                     break;
2813                   case H_GOT_REQ_HEADER:
2814                     ics_getting_history = H_GETTING_MOVES;
2815                     started = STARTED_MOVES;
2816                     parse_pos = 0;
2817                     if (oldi > next_out) {
2818                         SendToPlayer(&buf[next_out], oldi - next_out);
2819                     }
2820                     break;
2821                   case H_GOT_UNREQ_HEADER:
2822                     ics_getting_history = H_GETTING_MOVES;
2823                     started = STARTED_MOVES_NOHIDE;
2824                     parse_pos = 0;
2825                     break;
2826                   case H_GOT_UNWANTED_HEADER:
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                 }
2830                 continue;
2831             }                           
2832             
2833             if (looking_at(buf, &i, "% ") ||
2834                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2835                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2836                 savingComment = FALSE;
2837                 switch (started) {
2838                   case STARTED_MOVES:
2839                   case STARTED_MOVES_NOHIDE:
2840                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2841                     parse[parse_pos + i - oldi] = NULLCHAR;
2842                     ParseGameHistory(parse);
2843 #if ZIPPY
2844                     if (appData.zippyPlay && first.initDone) {
2845                         FeedMovesToProgram(&first, forwardMostMove);
2846                         if (gameMode == IcsPlayingWhite) {
2847                             if (WhiteOnMove(forwardMostMove)) {
2848                                 if (first.sendTime) {
2849                                   if (first.useColors) {
2850                                     SendToProgram("black\n", &first); 
2851                                   }
2852                                   SendTimeRemaining(&first, TRUE);
2853                                 }
2854                                 if (first.useColors) {
2855                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2856                                 }
2857                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2858                                 first.maybeThinking = TRUE;
2859                             } else {
2860                                 if (first.usePlayother) {
2861                                   if (first.sendTime) {
2862                                     SendTimeRemaining(&first, TRUE);
2863                                   }
2864                                   SendToProgram("playother\n", &first);
2865                                   firstMove = FALSE;
2866                                 } else {
2867                                   firstMove = TRUE;
2868                                 }
2869                             }
2870                         } else if (gameMode == IcsPlayingBlack) {
2871                             if (!WhiteOnMove(forwardMostMove)) {
2872                                 if (first.sendTime) {
2873                                   if (first.useColors) {
2874                                     SendToProgram("white\n", &first);
2875                                   }
2876                                   SendTimeRemaining(&first, FALSE);
2877                                 }
2878                                 if (first.useColors) {
2879                                   SendToProgram("black\n", &first);
2880                                 }
2881                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2882                                 first.maybeThinking = TRUE;
2883                             } else {
2884                                 if (first.usePlayother) {
2885                                   if (first.sendTime) {
2886                                     SendTimeRemaining(&first, FALSE);
2887                                   }
2888                                   SendToProgram("playother\n", &first);
2889                                   firstMove = FALSE;
2890                                 } else {
2891                                   firstMove = TRUE;
2892                                 }
2893                             }
2894                         }                       
2895                     }
2896 #endif
2897                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2898                         /* Moves came from oldmoves or moves command
2899                            while we weren't doing anything else.
2900                            */
2901                         currentMove = forwardMostMove;
2902                         ClearHighlights();/*!!could figure this out*/
2903                         flipView = appData.flipView;
2904                         DrawPosition(FALSE, boards[currentMove]);
2905                         DisplayBothClocks();
2906                         sprintf(str, "%s vs. %s",
2907                                 gameInfo.white, gameInfo.black);
2908                         DisplayTitle(str);
2909                         gameMode = IcsIdle;
2910                     } else {
2911                         /* Moves were history of an active game */
2912                         if (gameInfo.resultDetails != NULL) {
2913                             free(gameInfo.resultDetails);
2914                             gameInfo.resultDetails = NULL;
2915                         }
2916                     }
2917                     HistorySet(parseList, backwardMostMove,
2918                                forwardMostMove, currentMove-1);
2919                     DisplayMove(currentMove - 1);
2920                     if (started == STARTED_MOVES) next_out = i;
2921                     started = STARTED_NONE;
2922                     ics_getting_history = H_FALSE;
2923                     break;
2924
2925                   case STARTED_OBSERVE:
2926                     started = STARTED_NONE;
2927                     SendToICS(ics_prefix);
2928                     SendToICS("refresh\n");
2929                     break;
2930
2931                   default:
2932                     break;
2933                 }
2934                 if(bookHit) { // [HGM] book: simulate book reply
2935                     static char bookMove[MSG_SIZ]; // a bit generous?
2936
2937                     programStats.nodes = programStats.depth = programStats.time = 
2938                     programStats.score = programStats.got_only_move = 0;
2939                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2940
2941                     strcpy(bookMove, "move ");
2942                     strcat(bookMove, bookHit);
2943                     HandleMachineMove(bookMove, &first);
2944                 }
2945                 continue;
2946             }
2947             
2948             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2949                  started == STARTED_HOLDINGS ||
2950                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2951                 /* Accumulate characters in move list or board */
2952                 parse[parse_pos++] = buf[i];
2953             }
2954             
2955             /* Start of game messages.  Mostly we detect start of game
2956                when the first board image arrives.  On some versions
2957                of the ICS, though, we need to do a "refresh" after starting
2958                to observe in order to get the current board right away. */
2959             if (looking_at(buf, &i, "Adding game * to observation list")) {
2960                 started = STARTED_OBSERVE;
2961                 continue;
2962             }
2963
2964             /* Handle auto-observe */
2965             if (appData.autoObserve &&
2966                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2967                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2968                 char *player;
2969                 /* Choose the player that was highlighted, if any. */
2970                 if (star_match[0][0] == '\033' ||
2971                     star_match[1][0] != '\033') {
2972                     player = star_match[0];
2973                 } else {
2974                     player = star_match[2];
2975                 }
2976                 sprintf(str, "%sobserve %s\n",
2977                         ics_prefix, StripHighlightAndTitle(player));
2978                 SendToICS(str);
2979
2980                 /* Save ratings from notify string */
2981                 strcpy(player1Name, star_match[0]);
2982                 player1Rating = string_to_rating(star_match[1]);
2983                 strcpy(player2Name, star_match[2]);
2984                 player2Rating = string_to_rating(star_match[3]);
2985
2986                 if (appData.debugMode)
2987                   fprintf(debugFP, 
2988                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2989                           player1Name, player1Rating,
2990                           player2Name, player2Rating);
2991
2992                 continue;
2993             }
2994
2995             /* Deal with automatic examine mode after a game,
2996                and with IcsObserving -> IcsExamining transition */
2997             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2998                 looking_at(buf, &i, "has made you an examiner of game *")) {
2999
3000                 int gamenum = atoi(star_match[0]);
3001                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3002                     gamenum == ics_gamenum) {
3003                     /* We were already playing or observing this game;
3004                        no need to refetch history */
3005                     gameMode = IcsExamining;
3006                     if (pausing) {
3007                         pauseExamForwardMostMove = forwardMostMove;
3008                     } else if (currentMove < forwardMostMove) {
3009                         ForwardInner(forwardMostMove);
3010                     }
3011                 } else {
3012                     /* I don't think this case really can happen */
3013                     SendToICS(ics_prefix);
3014                     SendToICS("refresh\n");
3015                 }
3016                 continue;
3017             }    
3018             
3019             /* Error messages */
3020 //          if (ics_user_moved) {
3021             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3022                 if (looking_at(buf, &i, "Illegal move") ||
3023                     looking_at(buf, &i, "Not a legal move") ||
3024                     looking_at(buf, &i, "Your king is in check") ||
3025                     looking_at(buf, &i, "It isn't your turn") ||
3026                     looking_at(buf, &i, "It is not your move")) {
3027                     /* Illegal move */
3028                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3029                         currentMove = --forwardMostMove;
3030                         DisplayMove(currentMove - 1); /* before DMError */
3031                         DrawPosition(FALSE, boards[currentMove]);
3032                         SwitchClocks();
3033                         DisplayBothClocks();
3034                     }
3035                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3036                     ics_user_moved = 0;
3037                     continue;
3038                 }
3039             }
3040
3041             if (looking_at(buf, &i, "still have time") ||
3042                 looking_at(buf, &i, "not out of time") ||
3043                 looking_at(buf, &i, "either player is out of time") ||
3044                 looking_at(buf, &i, "has timeseal; checking")) {
3045                 /* We must have called his flag a little too soon */
3046                 whiteFlag = blackFlag = FALSE;
3047                 continue;
3048             }
3049
3050             if (looking_at(buf, &i, "added * seconds to") ||
3051                 looking_at(buf, &i, "seconds were added to")) {
3052                 /* Update the clocks */
3053                 SendToICS(ics_prefix);
3054                 SendToICS("refresh\n");
3055                 continue;
3056             }
3057
3058             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3059                 ics_clock_paused = TRUE;
3060                 StopClocks();
3061                 continue;
3062             }
3063
3064             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3065                 ics_clock_paused = FALSE;
3066                 StartClocks();
3067                 continue;
3068             }
3069
3070             /* Grab player ratings from the Creating: message.
3071                Note we have to check for the special case when
3072                the ICS inserts things like [white] or [black]. */
3073             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3074                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3075                 /* star_matches:
3076                    0    player 1 name (not necessarily white)
3077                    1    player 1 rating
3078                    2    empty, white, or black (IGNORED)
3079                    3    player 2 name (not necessarily black)
3080                    4    player 2 rating
3081                    
3082                    The names/ratings are sorted out when the game
3083                    actually starts (below).
3084                 */
3085                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3086                 player1Rating = string_to_rating(star_match[1]);
3087                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3088                 player2Rating = string_to_rating(star_match[4]);
3089
3090                 if (appData.debugMode)
3091                   fprintf(debugFP, 
3092                           "Ratings from 'Creating:' %s %d, %s %d\n",
3093                           player1Name, player1Rating,
3094                           player2Name, player2Rating);
3095
3096                 continue;
3097             }
3098             
3099             /* Improved generic start/end-of-game messages */
3100             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3101                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3102                 /* If tkind == 0: */
3103                 /* star_match[0] is the game number */
3104                 /*           [1] is the white player's name */
3105                 /*           [2] is the black player's name */
3106                 /* For end-of-game: */
3107                 /*           [3] is the reason for the game end */
3108                 /*           [4] is a PGN end game-token, preceded by " " */
3109                 /* For start-of-game: */
3110                 /*           [3] begins with "Creating" or "Continuing" */
3111                 /*           [4] is " *" or empty (don't care). */
3112                 int gamenum = atoi(star_match[0]);
3113                 char *whitename, *blackname, *why, *endtoken;
3114                 ChessMove endtype = (ChessMove) 0;
3115
3116                 if (tkind == 0) {
3117                   whitename = star_match[1];
3118                   blackname = star_match[2];
3119                   why = star_match[3];
3120                   endtoken = star_match[4];
3121                 } else {
3122                   whitename = star_match[1];
3123                   blackname = star_match[3];
3124                   why = star_match[5];
3125                   endtoken = star_match[6];
3126                 }
3127
3128                 /* Game start messages */
3129                 if (strncmp(why, "Creating ", 9) == 0 ||
3130                     strncmp(why, "Continuing ", 11) == 0) {
3131                     gs_gamenum = gamenum;
3132                     strcpy(gs_kind, strchr(why, ' ') + 1);
3133 #if ZIPPY
3134                     if (appData.zippyPlay) {
3135                         ZippyGameStart(whitename, blackname);
3136                     }
3137 #endif /*ZIPPY*/
3138                     continue;
3139                 }
3140
3141                 /* Game end messages */
3142                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3143                     ics_gamenum != gamenum) {
3144                     continue;
3145                 }
3146                 while (endtoken[0] == ' ') endtoken++;
3147                 switch (endtoken[0]) {
3148                   case '*':
3149                   default:
3150                     endtype = GameUnfinished;
3151                     break;
3152                   case '0':
3153                     endtype = BlackWins;
3154                     break;
3155                   case '1':
3156                     if (endtoken[1] == '/')
3157                       endtype = GameIsDrawn;
3158                     else
3159                       endtype = WhiteWins;
3160                     break;
3161                 }
3162                 GameEnds(endtype, why, GE_ICS);
3163 #if ZIPPY
3164                 if (appData.zippyPlay && first.initDone) {
3165                     ZippyGameEnd(endtype, why);
3166                     if (first.pr == NULL) {
3167                       /* Start the next process early so that we'll
3168                          be ready for the next challenge */
3169                       StartChessProgram(&first);
3170                     }
3171                     /* Send "new" early, in case this command takes
3172                        a long time to finish, so that we'll be ready
3173                        for the next challenge. */
3174                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3175                     Reset(TRUE, TRUE);
3176                 }
3177 #endif /*ZIPPY*/
3178                 continue;
3179             }
3180
3181             if (looking_at(buf, &i, "Removing game * from observation") ||
3182                 looking_at(buf, &i, "no longer observing game *") ||
3183                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3184                 if (gameMode == IcsObserving &&
3185                     atoi(star_match[0]) == ics_gamenum)
3186                   {
3187                       /* icsEngineAnalyze */
3188                       if (appData.icsEngineAnalyze) {
3189                             ExitAnalyzeMode();
3190                             ModeHighlight();
3191                       }
3192                       StopClocks();
3193                       gameMode = IcsIdle;
3194                       ics_gamenum = -1;
3195                       ics_user_moved = FALSE;
3196                   }
3197                 continue;
3198             }
3199
3200             if (looking_at(buf, &i, "no longer examining game *")) {
3201                 if (gameMode == IcsExamining &&
3202                     atoi(star_match[0]) == ics_gamenum)
3203                   {
3204                       gameMode = IcsIdle;
3205                       ics_gamenum = -1;
3206                       ics_user_moved = FALSE;
3207                   }
3208                 continue;
3209             }
3210
3211             /* Advance leftover_start past any newlines we find,
3212                so only partial lines can get reparsed */
3213             if (looking_at(buf, &i, "\n")) {
3214                 prevColor = curColor;
3215                 if (curColor != ColorNormal) {
3216                     if (oldi > next_out) {
3217                         SendToPlayer(&buf[next_out], oldi - next_out);
3218                         next_out = oldi;
3219                     }
3220                     Colorize(ColorNormal, FALSE);
3221                     curColor = ColorNormal;
3222                 }
3223                 if (started == STARTED_BOARD) {
3224                     started = STARTED_NONE;
3225                     parse[parse_pos] = NULLCHAR;
3226                     ParseBoard12(parse);
3227                     ics_user_moved = 0;
3228
3229                     /* Send premove here */
3230                     if (appData.premove) {
3231                       char str[MSG_SIZ];
3232                       if (currentMove == 0 &&
3233                           gameMode == IcsPlayingWhite &&
3234                           appData.premoveWhite) {
3235                         sprintf(str, "%s\n", appData.premoveWhiteText);
3236                         if (appData.debugMode)
3237                           fprintf(debugFP, "Sending premove:\n");
3238                         SendToICS(str);
3239                       } else if (currentMove == 1 &&
3240                                  gameMode == IcsPlayingBlack &&
3241                                  appData.premoveBlack) {
3242                         sprintf(str, "%s\n", appData.premoveBlackText);
3243                         if (appData.debugMode)
3244                           fprintf(debugFP, "Sending premove:\n");
3245                         SendToICS(str);
3246                       } else if (gotPremove) {
3247                         gotPremove = 0;
3248                         ClearPremoveHighlights();
3249                         if (appData.debugMode)
3250                           fprintf(debugFP, "Sending premove:\n");
3251                           UserMoveEvent(premoveFromX, premoveFromY, 
3252                                         premoveToX, premoveToY, 
3253                                         premovePromoChar);
3254                       }
3255                     }
3256
3257                     /* Usually suppress following prompt */
3258                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3259                         if (looking_at(buf, &i, "*% ")) {
3260                             savingComment = FALSE;
3261                         }
3262                     }
3263                     next_out = i;
3264                 } else if (started == STARTED_HOLDINGS) {
3265                     int gamenum;
3266                     char new_piece[MSG_SIZ];
3267                     started = STARTED_NONE;
3268                     parse[parse_pos] = NULLCHAR;
3269                     if (appData.debugMode)
3270                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3271                                                         parse, currentMove);
3272                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3273                         gamenum == ics_gamenum) {
3274                         if (gameInfo.variant == VariantNormal) {
3275                           /* [HGM] We seem to switch variant during a game!
3276                            * Presumably no holdings were displayed, so we have
3277                            * to move the position two files to the right to
3278                            * create room for them!
3279                            */
3280                           VariantClass newVariant;
3281                           switch(gameInfo.boardWidth) { // base guess on board width
3282                                 case 9:  newVariant = VariantShogi; break;
3283                                 case 10: newVariant = VariantGreat; break;
3284                                 default: newVariant = VariantCrazyhouse; break;
3285                           }
3286                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3287                           /* Get a move list just to see the header, which
3288                              will tell us whether this is really bug or zh */
3289                           if (ics_getting_history == H_FALSE) {
3290                             ics_getting_history = H_REQUESTED;
3291                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3292                             SendToICS(str);
3293                           }
3294                         }
3295                         new_piece[0] = NULLCHAR;
3296                         sscanf(parse, "game %d white [%s black [%s <- %s",
3297                                &gamenum, white_holding, black_holding,
3298                                new_piece);
3299                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3300                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3301                         /* [HGM] copy holdings to board holdings area */
3302                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3303                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3304 #if ZIPPY
3305                         if (appData.zippyPlay && first.initDone) {
3306                             ZippyHoldings(white_holding, black_holding,
3307                                           new_piece);
3308                         }
3309 #endif /*ZIPPY*/
3310                         if (tinyLayout || smallLayout) {
3311                             char wh[16], bh[16];
3312                             PackHolding(wh, white_holding);
3313                             PackHolding(bh, black_holding);
3314                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3315                                     gameInfo.white, gameInfo.black);
3316                         } else {
3317                             sprintf(str, "%s [%s] vs. %s [%s]",
3318                                     gameInfo.white, white_holding,
3319                                     gameInfo.black, black_holding);
3320                         }
3321
3322                         DrawPosition(FALSE, boards[currentMove]);
3323                         DisplayTitle(str);
3324                     }
3325                     /* Suppress following prompt */
3326                     if (looking_at(buf, &i, "*% ")) {
3327                         savingComment = FALSE;
3328                     }
3329                     next_out = i;
3330                 }
3331                 continue;
3332             }
3333
3334             i++;                /* skip unparsed character and loop back */
3335         }
3336         
3337         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3338             started != STARTED_HOLDINGS && i > next_out) {
3339             SendToPlayer(&buf[next_out], i - next_out);
3340             next_out = i;
3341         }
3342         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3343         
3344         leftover_len = buf_len - leftover_start;
3345         /* if buffer ends with something we couldn't parse,
3346            reparse it after appending the next read */
3347         
3348     } else if (count == 0) {
3349         RemoveInputSource(isr);
3350         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3351     } else {
3352         DisplayFatalError(_("Error reading from ICS"), error, 1);
3353     }
3354 }
3355
3356
3357 /* Board style 12 looks like this:
3358    
3359    <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
3360    
3361  * The "<12> " is stripped before it gets to this routine.  The two
3362  * trailing 0's (flip state and clock ticking) are later addition, and
3363  * some chess servers may not have them, or may have only the first.
3364  * Additional trailing fields may be added in the future.  
3365  */
3366
3367 #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"
3368
3369 #define RELATION_OBSERVING_PLAYED    0
3370 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3371 #define RELATION_PLAYING_MYMOVE      1
3372 #define RELATION_PLAYING_NOTMYMOVE  -1
3373 #define RELATION_EXAMINING           2
3374 #define RELATION_ISOLATED_BOARD     -3
3375 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3376
3377 void
3378 ParseBoard12(string)
3379      char *string;
3380
3381     GameMode newGameMode;
3382     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3383     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3384     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3385     char to_play, board_chars[200];
3386     char move_str[500], str[500], elapsed_time[500];
3387     char black[32], white[32];
3388     Board board;
3389     int prevMove = currentMove;
3390     int ticking = 2;
3391     ChessMove moveType;
3392     int fromX, fromY, toX, toY;
3393     char promoChar;
3394     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3395     char *bookHit = NULL; // [HGM] book
3396     Boolean weird = FALSE;
3397
3398     fromX = fromY = toX = toY = -1;
3399     
3400     newGame = FALSE;
3401
3402     if (appData.debugMode)
3403       fprintf(debugFP, _("Parsing board: %s\n"), string);
3404
3405     move_str[0] = NULLCHAR;
3406     elapsed_time[0] = NULLCHAR;
3407     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3408         int  i = 0, j;
3409         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3410             if(string[i] == ' ') { ranks++; files = 0; }
3411             else files++;
3412             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3413             i++;
3414         }
3415         for(j = 0; j <i; j++) board_chars[j] = string[j];
3416         board_chars[i] = '\0';
3417         string += i + 1;
3418     }
3419     n = sscanf(string, PATTERN, &to_play, &double_push,
3420                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3421                &gamenum, white, black, &relation, &basetime, &increment,
3422                &white_stren, &black_stren, &white_time, &black_time,
3423                &moveNum, str, elapsed_time, move_str, &ics_flip,
3424                &ticking);
3425
3426    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3427                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3428      /* [HGM] We seem to switch variant during a game!
3429       * Try to guess new variant from board size
3430       */
3431           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3432           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3433           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3434           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3435           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3436           if(!weird) newVariant = VariantNormal;
3437           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3438           /* Get a move list just to see the header, which
3439              will tell us whether this is really bug or zh */
3440           if (ics_getting_history == H_FALSE) {
3441             ics_getting_history = H_REQUESTED;
3442             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3443             SendToICS(str);
3444           }
3445     }
3446
3447     if (n < 21) {
3448         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3449         DisplayError(str, 0);
3450         return;
3451     }
3452
3453     /* Convert the move number to internal form */
3454     moveNum = (moveNum - 1) * 2;
3455     if (to_play == 'B') moveNum++;
3456     if (moveNum >= MAX_MOVES) {
3457       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3458                         0, 1);
3459       return;
3460     }
3461     
3462     switch (relation) {
3463       case RELATION_OBSERVING_PLAYED:
3464       case RELATION_OBSERVING_STATIC:
3465         if (gamenum == -1) {
3466             /* Old ICC buglet */
3467             relation = RELATION_OBSERVING_STATIC;
3468         }
3469         newGameMode = IcsObserving;
3470         break;
3471       case RELATION_PLAYING_MYMOVE:
3472       case RELATION_PLAYING_NOTMYMOVE:
3473         newGameMode =
3474           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3475             IcsPlayingWhite : IcsPlayingBlack;
3476         break;
3477       case RELATION_EXAMINING:
3478         newGameMode = IcsExamining;
3479         break;
3480       case RELATION_ISOLATED_BOARD:
3481       default:
3482         /* Just display this board.  If user was doing something else,
3483            we will forget about it until the next board comes. */ 
3484         newGameMode = IcsIdle;
3485         break;
3486       case RELATION_STARTING_POSITION:
3487         newGameMode = gameMode;
3488         break;
3489     }
3490     
3491     /* Modify behavior for initial board display on move listing
3492        of wild games.
3493        */
3494     switch (ics_getting_history) {
3495       case H_FALSE:
3496       case H_REQUESTED:
3497         break;
3498       case H_GOT_REQ_HEADER:
3499       case H_GOT_UNREQ_HEADER:
3500         /* This is the initial position of the current game */
3501         gamenum = ics_gamenum;
3502         moveNum = 0;            /* old ICS bug workaround */
3503         if (to_play == 'B') {
3504           startedFromSetupPosition = TRUE;
3505           blackPlaysFirst = TRUE;
3506           moveNum = 1;
3507           if (forwardMostMove == 0) forwardMostMove = 1;
3508           if (backwardMostMove == 0) backwardMostMove = 1;
3509           if (currentMove == 0) currentMove = 1;
3510         }
3511         newGameMode = gameMode;
3512         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3513         break;
3514       case H_GOT_UNWANTED_HEADER:
3515         /* This is an initial board that we don't want */
3516         return;
3517       case H_GETTING_MOVES:
3518         /* Should not happen */
3519         DisplayError(_("Error gathering move list: extra board"), 0);
3520         ics_getting_history = H_FALSE;
3521         return;
3522     }
3523     
3524     /* Take action if this is the first board of a new game, or of a
3525        different game than is currently being displayed.  */
3526     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3527         relation == RELATION_ISOLATED_BOARD) {
3528         
3529         /* Forget the old game and get the history (if any) of the new one */
3530         if (gameMode != BeginningOfGame) {
3531           Reset(TRUE, TRUE);
3532         }
3533         newGame = TRUE;
3534         if (appData.autoRaiseBoard) BoardToTop();
3535         prevMove = -3;
3536         if (gamenum == -1) {
3537             newGameMode = IcsIdle;
3538         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3539                    appData.getMoveList) {
3540             /* Need to get game history */
3541             ics_getting_history = H_REQUESTED;
3542             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3543             SendToICS(str);
3544         }
3545         
3546         /* Initially flip the board to have black on the bottom if playing
3547            black or if the ICS flip flag is set, but let the user change
3548            it with the Flip View button. */
3549         flipView = appData.autoFlipView ? 
3550           (newGameMode == IcsPlayingBlack) || ics_flip :
3551           appData.flipView;
3552         
3553         /* Done with values from previous mode; copy in new ones */
3554         gameMode = newGameMode;
3555         ModeHighlight();
3556         ics_gamenum = gamenum;
3557         if (gamenum == gs_gamenum) {
3558             int klen = strlen(gs_kind);
3559             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3560             sprintf(str, "ICS %s", gs_kind);
3561             gameInfo.event = StrSave(str);
3562         } else {
3563             gameInfo.event = StrSave("ICS game");
3564         }
3565         gameInfo.site = StrSave(appData.icsHost);
3566         gameInfo.date = PGNDate();
3567         gameInfo.round = StrSave("-");
3568         gameInfo.white = StrSave(white);
3569         gameInfo.black = StrSave(black);
3570         timeControl = basetime * 60 * 1000;
3571         timeControl_2 = 0;
3572         timeIncrement = increment * 1000;
3573         movesPerSession = 0;
3574         gameInfo.timeControl = TimeControlTagValue();
3575         VariantSwitch(board, StringToVariant(gameInfo.event) );
3576   if (appData.debugMode) {
3577     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3578     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3579     setbuf(debugFP, NULL);
3580   }
3581
3582         gameInfo.outOfBook = NULL;
3583         
3584         /* Do we have the ratings? */
3585         if (strcmp(player1Name, white) == 0 &&
3586             strcmp(player2Name, black) == 0) {
3587             if (appData.debugMode)
3588               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3589                       player1Rating, player2Rating);
3590             gameInfo.whiteRating = player1Rating;
3591             gameInfo.blackRating = player2Rating;
3592         } else if (strcmp(player2Name, white) == 0 &&
3593                    strcmp(player1Name, black) == 0) {
3594             if (appData.debugMode)
3595               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3596                       player2Rating, player1Rating);
3597             gameInfo.whiteRating = player2Rating;
3598             gameInfo.blackRating = player1Rating;
3599         }
3600         player1Name[0] = player2Name[0] = NULLCHAR;
3601
3602         /* Silence shouts if requested */
3603         if (appData.quietPlay &&
3604             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3605             SendToICS(ics_prefix);
3606             SendToICS("set shout 0\n");
3607         }
3608     }
3609     
3610     /* Deal with midgame name changes */
3611     if (!newGame) {
3612         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3613             if (gameInfo.white) free(gameInfo.white);
3614             gameInfo.white = StrSave(white);
3615         }
3616         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3617             if (gameInfo.black) free(gameInfo.black);
3618             gameInfo.black = StrSave(black);
3619         }
3620     }
3621     
3622     /* Throw away game result if anything actually changes in examine mode */
3623     if (gameMode == IcsExamining && !newGame) {
3624         gameInfo.result = GameUnfinished;
3625         if (gameInfo.resultDetails != NULL) {
3626             free(gameInfo.resultDetails);
3627             gameInfo.resultDetails = NULL;
3628         }
3629     }
3630     
3631     /* In pausing && IcsExamining mode, we ignore boards coming
3632        in if they are in a different variation than we are. */
3633     if (pauseExamInvalid) return;
3634     if (pausing && gameMode == IcsExamining) {
3635         if (moveNum <= pauseExamForwardMostMove) {
3636             pauseExamInvalid = TRUE;
3637             forwardMostMove = pauseExamForwardMostMove;
3638             return;
3639         }
3640     }
3641     
3642   if (appData.debugMode) {
3643     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3644   }
3645     /* Parse the board */
3646     for (k = 0; k < ranks; k++) {
3647       for (j = 0; j < files; j++)
3648         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3649       if(gameInfo.holdingsWidth > 1) {
3650            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3651            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3652       }
3653     }
3654     CopyBoard(boards[moveNum], board);
3655     if (moveNum == 0) {
3656         startedFromSetupPosition =
3657           !CompareBoards(board, initialPosition);
3658         if(startedFromSetupPosition)
3659             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3660     }
3661
3662     /* [HGM] Set castling rights. Take the outermost Rooks,
3663        to make it also work for FRC opening positions. Note that board12
3664        is really defective for later FRC positions, as it has no way to
3665        indicate which Rook can castle if they are on the same side of King.
3666        For the initial position we grant rights to the outermost Rooks,
3667        and remember thos rights, and we then copy them on positions
3668        later in an FRC game. This means WB might not recognize castlings with
3669        Rooks that have moved back to their original position as illegal,
3670        but in ICS mode that is not its job anyway.
3671     */
3672     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3673     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3674
3675         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3676             if(board[0][i] == WhiteRook) j = i;
3677         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3678         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3679             if(board[0][i] == WhiteRook) j = i;
3680         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3681         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3682             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3683         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3684         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3686         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687
3688         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3689         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3690             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3692             if(board[BOARD_HEIGHT-1][k] == bKing)
3693                 initialRights[5] = castlingRights[moveNum][5] = k;
3694     } else { int r;
3695         r = castlingRights[moveNum][0] = initialRights[0];
3696         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3697         r = castlingRights[moveNum][1] = initialRights[1];
3698         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3699         r = castlingRights[moveNum][3] = initialRights[3];
3700         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3701         r = castlingRights[moveNum][4] = initialRights[4];
3702         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3703         /* wildcastle kludge: always assume King has rights */
3704         r = castlingRights[moveNum][2] = initialRights[2];
3705         r = castlingRights[moveNum][5] = initialRights[5];
3706     }
3707     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3708     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3709
3710     
3711     if (ics_getting_history == H_GOT_REQ_HEADER ||
3712         ics_getting_history == H_GOT_UNREQ_HEADER) {
3713         /* This was an initial position from a move list, not
3714            the current position */
3715         return;
3716     }
3717     
3718     /* Update currentMove and known move number limits */
3719     newMove = newGame || moveNum > forwardMostMove;
3720
3721     if (newGame) {
3722         forwardMostMove = backwardMostMove = currentMove = moveNum;
3723         if (gameMode == IcsExamining && moveNum == 0) {
3724           /* Workaround for ICS limitation: we are not told the wild
3725              type when starting to examine a game.  But if we ask for
3726              the move list, the move list header will tell us */
3727             ics_getting_history = H_REQUESTED;
3728             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3729             SendToICS(str);
3730         }
3731     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3732                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3733 #if ZIPPY
3734         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3735         /* [HGM] applied this also to an engine that is silently watching        */
3736         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3737             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3738             gameInfo.variant == currentlyInitializedVariant) {
3739           takeback = forwardMostMove - moveNum;
3740           for (i = 0; i < takeback; i++) {
3741             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3742             SendToProgram("undo\n", &first);
3743           }
3744         }
3745 #endif
3746
3747         forwardMostMove = moveNum;
3748         if (!pausing || currentMove > forwardMostMove)
3749           currentMove = forwardMostMove;
3750     } else {
3751         /* New part of history that is not contiguous with old part */ 
3752         if (pausing && gameMode == IcsExamining) {
3753             pauseExamInvalid = TRUE;
3754             forwardMostMove = pauseExamForwardMostMove;
3755             return;
3756         }
3757         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3758 #if ZIPPY
3759             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3760                 // [HGM] when we will receive the move list we now request, it will be
3761                 // fed to the engine from the first move on. So if the engine is not
3762                 // in the initial position now, bring it there.
3763                 InitChessProgram(&first, 0);
3764             }
3765 #endif
3766             ics_getting_history = H_REQUESTED;
3767             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3768             SendToICS(str);
3769         }
3770         forwardMostMove = backwardMostMove = currentMove = moveNum;
3771     }
3772     
3773     /* Update the clocks */
3774     if (strchr(elapsed_time, '.')) {
3775       /* Time is in ms */
3776       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3777       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3778     } else {
3779       /* Time is in seconds */
3780       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3781       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3782     }
3783       
3784
3785 #if ZIPPY
3786     if (appData.zippyPlay && newGame &&
3787         gameMode != IcsObserving && gameMode != IcsIdle &&
3788         gameMode != IcsExamining)
3789       ZippyFirstBoard(moveNum, basetime, increment);
3790 #endif
3791     
3792     /* Put the move on the move list, first converting
3793        to canonical algebraic form. */
3794     if (moveNum > 0) {
3795   if (appData.debugMode) {
3796     if (appData.debugMode) { int f = forwardMostMove;
3797         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3798                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3799     }
3800     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3801     fprintf(debugFP, "moveNum = %d\n", moveNum);
3802     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3803     setbuf(debugFP, NULL);
3804   }
3805         if (moveNum <= backwardMostMove) {
3806             /* We don't know what the board looked like before
3807                this move.  Punt. */
3808             strcpy(parseList[moveNum - 1], move_str);
3809             strcat(parseList[moveNum - 1], " ");
3810             strcat(parseList[moveNum - 1], elapsed_time);
3811             moveList[moveNum - 1][0] = NULLCHAR;
3812         } else if (strcmp(move_str, "none") == 0) {
3813             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3814             /* Again, we don't know what the board looked like;
3815                this is really the start of the game. */
3816             parseList[moveNum - 1][0] = NULLCHAR;
3817             moveList[moveNum - 1][0] = NULLCHAR;
3818             backwardMostMove = moveNum;
3819             startedFromSetupPosition = TRUE;
3820             fromX = fromY = toX = toY = -1;
3821         } else {
3822           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3823           //                 So we parse the long-algebraic move string in stead of the SAN move
3824           int valid; char buf[MSG_SIZ], *prom;
3825
3826           // str looks something like "Q/a1-a2"; kill the slash
3827           if(str[1] == '/') 
3828                 sprintf(buf, "%c%s", str[0], str+2);
3829           else  strcpy(buf, str); // might be castling
3830           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3831                 strcat(buf, prom); // long move lacks promo specification!
3832           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3833                 if(appData.debugMode) 
3834                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3835                 strcpy(move_str, buf);
3836           }
3837           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3838                                 &fromX, &fromY, &toX, &toY, &promoChar)
3839                || ParseOneMove(buf, moveNum - 1, &moveType,
3840                                 &fromX, &fromY, &toX, &toY, &promoChar);
3841           // end of long SAN patch
3842           if (valid) {
3843             (void) CoordsToAlgebraic(boards[moveNum - 1],
3844                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3845                                      fromY, fromX, toY, toX, promoChar,
3846                                      parseList[moveNum-1]);
3847             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3848                              castlingRights[moveNum]) ) {
3849               case MT_NONE:
3850               case MT_STALEMATE:
3851               default:
3852                 break;
3853               case MT_CHECK:
3854                 if(gameInfo.variant != VariantShogi)
3855                     strcat(parseList[moveNum - 1], "+");
3856                 break;
3857               case MT_CHECKMATE:
3858               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3859                 strcat(parseList[moveNum - 1], "#");
3860                 break;
3861             }
3862             strcat(parseList[moveNum - 1], " ");
3863             strcat(parseList[moveNum - 1], elapsed_time);
3864             /* currentMoveString is set as a side-effect of ParseOneMove */
3865             strcpy(moveList[moveNum - 1], currentMoveString);
3866             strcat(moveList[moveNum - 1], "\n");
3867           } else {
3868             /* Move from ICS was illegal!?  Punt. */
3869   if (appData.debugMode) {
3870     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3871     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3872   }
3873             strcpy(parseList[moveNum - 1], move_str);
3874             strcat(parseList[moveNum - 1], " ");
3875             strcat(parseList[moveNum - 1], elapsed_time);
3876             moveList[moveNum - 1][0] = NULLCHAR;
3877             fromX = fromY = toX = toY = -1;
3878           }
3879         }
3880   if (appData.debugMode) {
3881     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3882     setbuf(debugFP, NULL);
3883   }
3884
3885 #if ZIPPY
3886         /* Send move to chess program (BEFORE animating it). */
3887         if (appData.zippyPlay && !newGame && newMove && 
3888            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3889
3890             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3891                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3892                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3893                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3894                             move_str);
3895                     DisplayError(str, 0);
3896                 } else {
3897                     if (first.sendTime) {
3898                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3899                     }
3900                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3901                     if (firstMove && !bookHit) {
3902                         firstMove = FALSE;
3903                         if (first.useColors) {
3904                           SendToProgram(gameMode == IcsPlayingWhite ?
3905                                         "white\ngo\n" :
3906                                         "black\ngo\n", &first);
3907                         } else {
3908                           SendToProgram("go\n", &first);
3909                         }
3910                         first.maybeThinking = TRUE;
3911                     }
3912                 }
3913             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3914               if (moveList[moveNum - 1][0] == NULLCHAR) {
3915                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3916                 DisplayError(str, 0);
3917               } else {
3918                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3919                 SendMoveToProgram(moveNum - 1, &first);
3920               }
3921             }
3922         }
3923 #endif
3924     }
3925
3926     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3927         /* If move comes from a remote source, animate it.  If it
3928            isn't remote, it will have already been animated. */
3929         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3930             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3931         }
3932         if (!pausing && appData.highlightLastMove) {
3933             SetHighlights(fromX, fromY, toX, toY);
3934         }
3935     }
3936     
3937     /* Start the clocks */
3938     whiteFlag = blackFlag = FALSE;
3939     appData.clockMode = !(basetime == 0 && increment == 0);
3940     if (ticking == 0) {
3941       ics_clock_paused = TRUE;
3942       StopClocks();
3943     } else if (ticking == 1) {
3944       ics_clock_paused = FALSE;
3945     }
3946     if (gameMode == IcsIdle ||
3947         relation == RELATION_OBSERVING_STATIC ||
3948         relation == RELATION_EXAMINING ||
3949         ics_clock_paused)
3950       DisplayBothClocks();
3951     else
3952       StartClocks();
3953     
3954     /* Display opponents and material strengths */
3955     if (gameInfo.variant != VariantBughouse &&
3956         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3957         if (tinyLayout || smallLayout) {
3958             if(gameInfo.variant == VariantNormal)
3959                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3960                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3961                     basetime, increment);
3962             else
3963                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3964                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3965                     basetime, increment, (int) gameInfo.variant);
3966         } else {
3967             if(gameInfo.variant == VariantNormal)
3968                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3969                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3970                     basetime, increment);
3971             else
3972                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3974                     basetime, increment, VariantName(gameInfo.variant));
3975         }
3976         DisplayTitle(str);
3977   if (appData.debugMode) {
3978     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3979   }
3980     }
3981
3982    
3983     /* Display the board */
3984     if (!pausing && !appData.noGUI) {
3985       
3986       if (appData.premove)
3987           if (!gotPremove || 
3988              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3989              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3990               ClearPremoveHighlights();
3991
3992       DrawPosition(FALSE, boards[currentMove]);
3993       DisplayMove(moveNum - 1);
3994       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3995             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3996               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3997         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3998       }
3999     }
4000
4001     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4002 #if ZIPPY
4003     if(bookHit) { // [HGM] book: simulate book reply
4004         static char bookMove[MSG_SIZ]; // a bit generous?
4005
4006         programStats.nodes = programStats.depth = programStats.time = 
4007         programStats.score = programStats.got_only_move = 0;
4008         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4009
4010         strcpy(bookMove, "move ");
4011         strcat(bookMove, bookHit);
4012         HandleMachineMove(bookMove, &first);
4013     }
4014 #endif
4015 }
4016
4017 void
4018 GetMoveListEvent()
4019 {
4020     char buf[MSG_SIZ];
4021     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4022         ics_getting_history = H_REQUESTED;
4023         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4024         SendToICS(buf);
4025     }
4026 }
4027
4028 void
4029 AnalysisPeriodicEvent(force)
4030      int force;
4031 {
4032     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4033          && !force) || !appData.periodicUpdates)
4034       return;
4035
4036     /* Send . command to Crafty to collect stats */
4037     SendToProgram(".\n", &first);
4038
4039     /* Don't send another until we get a response (this makes
4040        us stop sending to old Crafty's which don't understand
4041        the "." command (sending illegal cmds resets node count & time,
4042        which looks bad)) */
4043     programStats.ok_to_send = 0;
4044 }
4045
4046 void ics_update_width(new_width)
4047         int new_width;
4048 {
4049         ics_printf("set width %d\n", new_width);
4050 }
4051
4052 void
4053 SendMoveToProgram(moveNum, cps)
4054      int moveNum;
4055      ChessProgramState *cps;
4056 {
4057     char buf[MSG_SIZ];
4058
4059     if (cps->useUsermove) {
4060       SendToProgram("usermove ", cps);
4061     }
4062     if (cps->useSAN) {
4063       char *space;
4064       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4065         int len = space - parseList[moveNum];
4066         memcpy(buf, parseList[moveNum], len);
4067         buf[len++] = '\n';
4068         buf[len] = NULLCHAR;
4069       } else {
4070         sprintf(buf, "%s\n", parseList[moveNum]);
4071       }
4072       SendToProgram(buf, cps);
4073     } else {
4074       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4075         AlphaRank(moveList[moveNum], 4);
4076         SendToProgram(moveList[moveNum], cps);
4077         AlphaRank(moveList[moveNum], 4); // and back
4078       } else
4079       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4080        * the engine. It would be nice to have a better way to identify castle 
4081        * moves here. */
4082       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4083                                                                          && cps->useOOCastle) {
4084         int fromX = moveList[moveNum][0] - AAA; 
4085         int fromY = moveList[moveNum][1] - ONE;
4086         int toX = moveList[moveNum][2] - AAA; 
4087         int toY = moveList[moveNum][3] - ONE;
4088         if((boards[moveNum][fromY][fromX] == WhiteKing 
4089             && boards[moveNum][toY][toX] == WhiteRook)
4090            || (boards[moveNum][fromY][fromX] == BlackKing 
4091                && boards[moveNum][toY][toX] == BlackRook)) {
4092           if(toX > fromX) SendToProgram("O-O\n", cps);
4093           else SendToProgram("O-O-O\n", cps);
4094         }
4095         else SendToProgram(moveList[moveNum], cps);
4096       }
4097       else SendToProgram(moveList[moveNum], cps);
4098       /* End of additions by Tord */
4099     }
4100
4101     /* [HGM] setting up the opening has brought engine in force mode! */
4102     /*       Send 'go' if we are in a mode where machine should play. */
4103     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4104         (gameMode == TwoMachinesPlay   ||
4105 #ifdef ZIPPY
4106          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4107 #endif
4108          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4109         SendToProgram("go\n", cps);
4110   if (appData.debugMode) {
4111     fprintf(debugFP, "(extra)\n");
4112   }
4113     }
4114     setboardSpoiledMachineBlack = 0;
4115 }
4116
4117 void
4118 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4119      ChessMove moveType;
4120      int fromX, fromY, toX, toY;
4121 {
4122     char user_move[MSG_SIZ];
4123
4124     switch (moveType) {
4125       default:
4126         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4127                 (int)moveType, fromX, fromY, toX, toY);
4128         DisplayError(user_move + strlen("say "), 0);
4129         break;
4130       case WhiteKingSideCastle:
4131       case BlackKingSideCastle:
4132       case WhiteQueenSideCastleWild:
4133       case BlackQueenSideCastleWild:
4134       /* PUSH Fabien */
4135       case WhiteHSideCastleFR:
4136       case BlackHSideCastleFR:
4137       /* POP Fabien */
4138         sprintf(user_move, "o-o\n");
4139         break;
4140       case WhiteQueenSideCastle:
4141       case BlackQueenSideCastle:
4142       case WhiteKingSideCastleWild:
4143       case BlackKingSideCastleWild:
4144       /* PUSH Fabien */
4145       case WhiteASideCastleFR:
4146       case BlackASideCastleFR:
4147       /* POP Fabien */
4148         sprintf(user_move, "o-o-o\n");
4149         break;
4150       case WhitePromotionQueen:
4151       case BlackPromotionQueen:
4152       case WhitePromotionRook:
4153       case BlackPromotionRook:
4154       case WhitePromotionBishop:
4155       case BlackPromotionBishop:
4156       case WhitePromotionKnight:
4157       case BlackPromotionKnight:
4158       case WhitePromotionKing:
4159       case BlackPromotionKing:
4160       case WhitePromotionChancellor:
4161       case BlackPromotionChancellor:
4162       case WhitePromotionArchbishop:
4163       case BlackPromotionArchbishop:
4164         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4165             sprintf(user_move, "%c%c%c%c=%c\n",
4166                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4167                 PieceToChar(WhiteFerz));
4168         else if(gameInfo.variant == VariantGreat)
4169             sprintf(user_move, "%c%c%c%c=%c\n",
4170                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4171                 PieceToChar(WhiteMan));
4172         else
4173             sprintf(user_move, "%c%c%c%c=%c\n",
4174                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4175                 PieceToChar(PromoPiece(moveType)));
4176         break;
4177       case WhiteDrop:
4178       case BlackDrop:
4179         sprintf(user_move, "%c@%c%c\n",
4180                 ToUpper(PieceToChar((ChessSquare) fromX)),
4181                 AAA + toX, ONE + toY);
4182         break;
4183       case NormalMove:
4184       case WhiteCapturesEnPassant:
4185       case BlackCapturesEnPassant:
4186       case IllegalMove:  /* could be a variant we don't quite understand */
4187         sprintf(user_move, "%c%c%c%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4189         break;
4190     }
4191     SendToICS(user_move);
4192     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4193         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4194 }
4195
4196 void
4197 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4198      int rf, ff, rt, ft;
4199      char promoChar;
4200      char move[7];
4201 {
4202     if (rf == DROP_RANK) {
4203         sprintf(move, "%c@%c%c\n",
4204                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4205     } else {
4206         if (promoChar == 'x' || promoChar == NULLCHAR) {
4207             sprintf(move, "%c%c%c%c\n",
4208                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4209         } else {
4210             sprintf(move, "%c%c%c%c%c\n",
4211                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4212         }
4213     }
4214 }
4215
4216 void
4217 ProcessICSInitScript(f)
4218      FILE *f;
4219 {
4220     char buf[MSG_SIZ];
4221
4222     while (fgets(buf, MSG_SIZ, f)) {
4223         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4224     }
4225
4226     fclose(f);
4227 }
4228
4229
4230 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4231 void
4232 AlphaRank(char *move, int n)
4233 {
4234 //    char *p = move, c; int x, y;
4235
4236     if (appData.debugMode) {
4237         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4238     }
4239
4240     if(move[1]=='*' && 
4241        move[2]>='0' && move[2]<='9' &&
4242        move[3]>='a' && move[3]<='x'    ) {
4243         move[1] = '@';
4244         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4245         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4246     } else
4247     if(move[0]>='0' && move[0]<='9' &&
4248        move[1]>='a' && move[1]<='x' &&
4249        move[2]>='0' && move[2]<='9' &&
4250        move[3]>='a' && move[3]<='x'    ) {
4251         /* input move, Shogi -> normal */
4252         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4253         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4254         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4255         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4256     } else
4257     if(move[1]=='@' &&
4258        move[3]>='0' && move[3]<='9' &&
4259        move[2]>='a' && move[2]<='x'    ) {
4260         move[1] = '*';
4261         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4262         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4263     } else
4264     if(
4265        move[0]>='a' && move[0]<='x' &&
4266        move[3]>='0' && move[3]<='9' &&
4267        move[2]>='a' && move[2]<='x'    ) {
4268          /* output move, normal -> Shogi */
4269         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4270         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4271         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4272         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4273         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4274     }
4275     if (appData.debugMode) {
4276         fprintf(debugFP, "   out = '%s'\n", move);
4277     }
4278 }
4279
4280 /* Parser for moves from gnuchess, ICS, or user typein box */
4281 Boolean
4282 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4283      char *move;
4284      int moveNum;
4285      ChessMove *moveType;
4286      int *fromX, *fromY, *toX, *toY;
4287      char *promoChar;
4288 {       
4289     if (appData.debugMode) {
4290         fprintf(debugFP, "move to parse: %s\n", move);
4291     }
4292     *moveType = yylexstr(moveNum, move);
4293
4294     switch (*moveType) {
4295       case WhitePromotionChancellor:
4296       case BlackPromotionChancellor:
4297       case WhitePromotionArchbishop:
4298       case BlackPromotionArchbishop:
4299       case WhitePromotionQueen:
4300       case BlackPromotionQueen:
4301       case WhitePromotionRook:
4302       case BlackPromotionRook:
4303       case WhitePromotionBishop:
4304       case BlackPromotionBishop:
4305       case WhitePromotionKnight:
4306       case BlackPromotionKnight:
4307       case WhitePromotionKing:
4308       case BlackPromotionKing:
4309       case NormalMove:
4310       case WhiteCapturesEnPassant:
4311       case BlackCapturesEnPassant:
4312       case WhiteKingSideCastle:
4313       case WhiteQueenSideCastle:
4314       case BlackKingSideCastle:
4315       case BlackQueenSideCastle:
4316       case WhiteKingSideCastleWild:
4317       case WhiteQueenSideCastleWild:
4318       case BlackKingSideCastleWild:
4319       case BlackQueenSideCastleWild:
4320       /* Code added by Tord: */
4321       case WhiteHSideCastleFR:
4322       case WhiteASideCastleFR:
4323       case BlackHSideCastleFR:
4324       case BlackASideCastleFR:
4325       /* End of code added by Tord */
4326       case IllegalMove:         /* bug or odd chess variant */
4327         *fromX = currentMoveString[0] - AAA;
4328         *fromY = currentMoveString[1] - ONE;
4329         *toX = currentMoveString[2] - AAA;
4330         *toY = currentMoveString[3] - ONE;
4331         *promoChar = currentMoveString[4];
4332         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4333             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4334     if (appData.debugMode) {
4335         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4336     }
4337             *fromX = *fromY = *toX = *toY = 0;
4338             return FALSE;
4339         }
4340         if (appData.testLegality) {
4341           return (*moveType != IllegalMove);
4342         } else {
4343           return !(fromX == fromY && toX == toY);
4344         }
4345
4346       case WhiteDrop:
4347       case BlackDrop:
4348         *fromX = *moveType == WhiteDrop ?
4349           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4350           (int) CharToPiece(ToLower(currentMoveString[0]));
4351         *fromY = DROP_RANK;
4352         *toX = currentMoveString[2] - AAA;
4353         *toY = currentMoveString[3] - ONE;
4354         *promoChar = NULLCHAR;
4355         return TRUE;
4356
4357       case AmbiguousMove:
4358       case ImpossibleMove:
4359       case (ChessMove) 0:       /* end of file */
4360       case ElapsedTime:
4361       case Comment:
4362       case PGNTag:
4363       case NAG:
4364       case WhiteWins:
4365       case BlackWins:
4366       case GameIsDrawn:
4367       default:
4368     if (appData.debugMode) {
4369         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4370     }
4371         /* bug? */
4372         *fromX = *fromY = *toX = *toY = 0;
4373         *promoChar = NULLCHAR;
4374         return FALSE;
4375     }
4376 }
4377
4378 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4379 // All positions will have equal probability, but the current method will not provide a unique
4380 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4381 #define DARK 1
4382 #define LITE 2
4383 #define ANY 3
4384
4385 int squaresLeft[4];
4386 int piecesLeft[(int)BlackPawn];
4387 int seed, nrOfShuffles;
4388
4389 void GetPositionNumber()
4390 {       // sets global variable seed
4391         int i;
4392
4393         seed = appData.defaultFrcPosition;
4394         if(seed < 0) { // randomize based on time for negative FRC position numbers
4395                 for(i=0; i<50; i++) seed += random();
4396                 seed = random() ^ random() >> 8 ^ random() << 8;
4397                 if(seed<0) seed = -seed;
4398         }
4399 }
4400
4401 int put(Board board, int pieceType, int rank, int n, int shade)
4402 // put the piece on the (n-1)-th empty squares of the given shade
4403 {
4404         int i;
4405
4406         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4407                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4408                         board[rank][i] = (ChessSquare) pieceType;
4409                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4410                         squaresLeft[ANY]--;
4411                         piecesLeft[pieceType]--; 
4412                         return i;
4413                 }
4414         }
4415         return -1;
4416 }
4417
4418
4419 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4420 // calculate where the next piece goes, (any empty square), and put it there
4421 {
4422         int i;
4423
4424         i = seed % squaresLeft[shade];
4425         nrOfShuffles *= squaresLeft[shade];
4426         seed /= squaresLeft[shade];
4427         put(board, pieceType, rank, i, shade);
4428 }
4429
4430 void AddTwoPieces(Board board, int pieceType, int rank)
4431 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4432 {
4433         int i, n=squaresLeft[ANY], j=n-1, k;
4434
4435         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4436         i = seed % k;  // pick one
4437         nrOfShuffles *= k;
4438         seed /= k;
4439         while(i >= j) i -= j--;
4440         j = n - 1 - j; i += j;
4441         put(board, pieceType, rank, j, ANY);
4442         put(board, pieceType, rank, i, ANY);
4443 }
4444
4445 void SetUpShuffle(Board board, int number)
4446 {
4447         int i, p, first=1;
4448
4449         GetPositionNumber(); nrOfShuffles = 1;
4450
4451         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4452         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4453         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4454
4455         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4456
4457         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4458             p = (int) board[0][i];
4459             if(p < (int) BlackPawn) piecesLeft[p] ++;
4460             board[0][i] = EmptySquare;
4461         }
4462
4463         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4464             // shuffles restricted to allow normal castling put KRR first
4465             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4466                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4467             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4468                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4469             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4470                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4471             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4472                 put(board, WhiteRook, 0, 0, ANY);
4473             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4474         }
4475
4476         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4477             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4478             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4479                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4480                 while(piecesLeft[p] >= 2) {
4481                     AddOnePiece(board, p, 0, LITE);
4482                     AddOnePiece(board, p, 0, DARK);
4483                 }
4484                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4485             }
4486
4487         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4488             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4489             // but we leave King and Rooks for last, to possibly obey FRC restriction
4490             if(p == (int)WhiteRook) continue;
4491             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4492             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4493         }
4494
4495         // now everything is placed, except perhaps King (Unicorn) and Rooks
4496
4497         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4498             // Last King gets castling rights
4499             while(piecesLeft[(int)WhiteUnicorn]) {
4500                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4501                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4502             }
4503
4504             while(piecesLeft[(int)WhiteKing]) {
4505                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4506                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4507             }
4508
4509
4510         } else {
4511             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4512             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4513         }
4514
4515         // Only Rooks can be left; simply place them all
4516         while(piecesLeft[(int)WhiteRook]) {
4517                 i = put(board, WhiteRook, 0, 0, ANY);
4518                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4519                         if(first) {
4520                                 first=0;
4521                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4522                         }
4523                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4524                 }
4525         }
4526         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4527             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4528         }
4529
4530         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4531 }
4532
4533 int SetCharTable( char *table, const char * map )
4534 /* [HGM] moved here from winboard.c because of its general usefulness */
4535 /*       Basically a safe strcpy that uses the last character as King */
4536 {
4537     int result = FALSE; int NrPieces;
4538
4539     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4540                     && NrPieces >= 12 && !(NrPieces&1)) {
4541         int i; /* [HGM] Accept even length from 12 to 34 */
4542
4543         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4544         for( i=0; i<NrPieces/2-1; i++ ) {
4545             table[i] = map[i];
4546             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4547         }
4548         table[(int) WhiteKing]  = map[NrPieces/2-1];
4549         table[(int) BlackKing]  = map[NrPieces-1];
4550
4551         result = TRUE;
4552     }
4553
4554     return result;
4555 }
4556
4557 void Prelude(Board board)
4558 {       // [HGM] superchess: random selection of exo-pieces
4559         int i, j, k; ChessSquare p; 
4560         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4561
4562         GetPositionNumber(); // use FRC position number
4563
4564         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4565             SetCharTable(pieceToChar, appData.pieceToCharTable);
4566             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4567                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4568         }
4569
4570         j = seed%4;                 seed /= 4; 
4571         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4572         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4573         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4574         j = seed%3 + (seed%3 >= j); seed /= 3; 
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; 
4579         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2; 
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4587         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4588         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4589         put(board, exoPieces[0],    0, 0, ANY);
4590         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4591 }
4592
4593 void
4594 InitPosition(redraw)
4595      int redraw;
4596 {
4597     ChessSquare (* pieces)[BOARD_SIZE];
4598     int i, j, pawnRow, overrule,
4599     oldx = gameInfo.boardWidth,
4600     oldy = gameInfo.boardHeight,
4601     oldh = gameInfo.holdingsWidth,
4602     oldv = gameInfo.variant;
4603
4604     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4605
4606     /* [AS] Initialize pv info list [HGM] and game status */
4607     {
4608         for( i=0; i<MAX_MOVES; i++ ) {
4609             pvInfoList[i].depth = 0;
4610             epStatus[i]=EP_NONE;
4611             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4612         }
4613
4614         initialRulePlies = 0; /* 50-move counter start */
4615
4616         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4617         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4618     }
4619
4620     
4621     /* [HGM] logic here is completely changed. In stead of full positions */
4622     /* the initialized data only consist of the two backranks. The switch */
4623     /* selects which one we will use, which is than copied to the Board   */
4624     /* initialPosition, which for the rest is initialized by Pawns and    */
4625     /* empty squares. This initial position is then copied to boards[0],  */
4626     /* possibly after shuffling, so that it remains available.            */
4627
4628     gameInfo.holdingsWidth = 0; /* default board sizes */
4629     gameInfo.boardWidth    = 8;
4630     gameInfo.boardHeight   = 8;
4631     gameInfo.holdingsSize  = 0;
4632     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4633     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4634     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4635
4636     switch (gameInfo.variant) {
4637     case VariantFischeRandom:
4638       shuffleOpenings = TRUE;
4639     default:
4640       pieces = FIDEArray;
4641       break;
4642     case VariantShatranj:
4643       pieces = ShatranjArray;
4644       nrCastlingRights = 0;
4645       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4646       break;
4647     case VariantTwoKings:
4648       pieces = twoKingsArray;
4649       break;
4650     case VariantCapaRandom:
4651       shuffleOpenings = TRUE;
4652     case VariantCapablanca:
4653       pieces = CapablancaArray;
4654       gameInfo.boardWidth = 10;
4655       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4656       break;
4657     case VariantGothic:
4658       pieces = GothicArray;
4659       gameInfo.boardWidth = 10;
4660       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4661       break;
4662     case VariantJanus:
4663       pieces = JanusArray;
4664       gameInfo.boardWidth = 10;
4665       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4666       nrCastlingRights = 6;
4667         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4668         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4669         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4670         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4671         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4672         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4673       break;
4674     case VariantFalcon:
4675       pieces = FalconArray;
4676       gameInfo.boardWidth = 10;
4677       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4678       break;
4679     case VariantXiangqi:
4680       pieces = XiangqiArray;
4681       gameInfo.boardWidth  = 9;
4682       gameInfo.boardHeight = 10;
4683       nrCastlingRights = 0;
4684       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4685       break;
4686     case VariantShogi:
4687       pieces = ShogiArray;
4688       gameInfo.boardWidth  = 9;
4689       gameInfo.boardHeight = 9;
4690       gameInfo.holdingsSize = 7;
4691       nrCastlingRights = 0;
4692       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4693       break;
4694     case VariantCourier:
4695       pieces = CourierArray;
4696       gameInfo.boardWidth  = 12;
4697       nrCastlingRights = 0;
4698       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4699       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4700       break;
4701     case VariantKnightmate:
4702       pieces = KnightmateArray;
4703       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4704       break;
4705     case VariantFairy:
4706       pieces = fairyArray;
4707       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4708       break;
4709     case VariantGreat:
4710       pieces = GreatArray;
4711       gameInfo.boardWidth = 10;
4712       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4713       gameInfo.holdingsSize = 8;
4714       break;
4715     case VariantSuper:
4716       pieces = FIDEArray;
4717       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4718       gameInfo.holdingsSize = 8;
4719       startedFromSetupPosition = TRUE;
4720       break;
4721     case VariantCrazyhouse:
4722     case VariantBughouse:
4723       pieces = FIDEArray;
4724       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4725       gameInfo.holdingsSize = 5;
4726       break;
4727     case VariantWildCastle:
4728       pieces = FIDEArray;
4729       /* !!?shuffle with kings guaranteed to be on d or e file */
4730       shuffleOpenings = 1;
4731       break;
4732     case VariantNoCastle:
4733       pieces = FIDEArray;
4734       nrCastlingRights = 0;
4735       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4736       /* !!?unconstrained back-rank shuffle */
4737       shuffleOpenings = 1;
4738       break;
4739     }
4740
4741     overrule = 0;
4742     if(appData.NrFiles >= 0) {
4743         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4744         gameInfo.boardWidth = appData.NrFiles;
4745     }
4746     if(appData.NrRanks >= 0) {
4747         gameInfo.boardHeight = appData.NrRanks;
4748     }
4749     if(appData.holdingsSize >= 0) {
4750         i = appData.holdingsSize;
4751         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4752         gameInfo.holdingsSize = i;
4753     }
4754     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4755     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4756         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4757
4758     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4759     if(pawnRow < 1) pawnRow = 1;
4760
4761     /* User pieceToChar list overrules defaults */
4762     if(appData.pieceToCharTable != NULL)
4763         SetCharTable(pieceToChar, appData.pieceToCharTable);
4764
4765     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4766
4767         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4768             s = (ChessSquare) 0; /* account holding counts in guard band */
4769         for( i=0; i<BOARD_HEIGHT; i++ )
4770             initialPosition[i][j] = s;
4771
4772         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4773         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4774         initialPosition[pawnRow][j] = WhitePawn;
4775         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4776         if(gameInfo.variant == VariantXiangqi) {
4777             if(j&1) {
4778                 initialPosition[pawnRow][j] = 
4779                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4780                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4781                    initialPosition[2][j] = WhiteCannon;
4782                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4783                 }
4784             }
4785         }
4786         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4787     }
4788     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4789
4790             j=BOARD_LEFT+1;
4791             initialPosition[1][j] = WhiteBishop;
4792             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4793             j=BOARD_RGHT-2;
4794             initialPosition[1][j] = WhiteRook;
4795             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4796     }
4797
4798     if( nrCastlingRights == -1) {
4799         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4800         /*       This sets default castling rights from none to normal corners   */
4801         /* Variants with other castling rights must set them themselves above    */
4802         nrCastlingRights = 6;
4803        
4804         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4805         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4806         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4807         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4808         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4809         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4810      }
4811
4812      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4813      if(gameInfo.variant == VariantGreat) { // promotion commoners
4814         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4815         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4816         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4817         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4818      }
4819   if (appData.debugMode) {
4820     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4821   }
4822     if(shuffleOpenings) {
4823         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4824         startedFromSetupPosition = TRUE;
4825     }
4826     if(startedFromPositionFile) {
4827       /* [HGM] loadPos: use PositionFile for every new game */
4828       CopyBoard(initialPosition, filePosition);
4829       for(i=0; i<nrCastlingRights; i++)
4830           castlingRights[0][i] = initialRights[i] = fileRights[i];
4831       startedFromSetupPosition = TRUE;
4832     }
4833
4834     CopyBoard(boards[0], initialPosition);
4835
4836     if(oldx != gameInfo.boardWidth ||
4837        oldy != gameInfo.boardHeight ||
4838        oldh != gameInfo.holdingsWidth
4839 #ifdef GOTHIC
4840        || oldv == VariantGothic ||        // For licensing popups
4841        gameInfo.variant == VariantGothic
4842 #endif
4843 #ifdef FALCON
4844        || oldv == VariantFalcon ||
4845        gameInfo.variant == VariantFalcon
4846 #endif
4847                                          )
4848             InitDrawingSizes(-2 ,0);
4849
4850     if (redraw)
4851       DrawPosition(TRUE, boards[currentMove]);
4852 }
4853
4854 void
4855 SendBoard(cps, moveNum)
4856      ChessProgramState *cps;
4857      int moveNum;
4858 {
4859     char message[MSG_SIZ];
4860     
4861     if (cps->useSetboard) {
4862       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4863       sprintf(message, "setboard %s\n", fen);
4864       SendToProgram(message, cps);
4865       free(fen);
4866
4867     } else {
4868       ChessSquare *bp;
4869       int i, j;
4870       /* Kludge to set black to move, avoiding the troublesome and now
4871        * deprecated "black" command.
4872        */
4873       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4874
4875       SendToProgram("edit\n", cps);
4876       SendToProgram("#\n", cps);
4877       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4878         bp = &boards[moveNum][i][BOARD_LEFT];
4879         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4880           if ((int) *bp < (int) BlackPawn) {
4881             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4882                     AAA + j, ONE + i);
4883             if(message[0] == '+' || message[0] == '~') {
4884                 sprintf(message, "%c%c%c+\n",
4885                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4886                         AAA + j, ONE + i);
4887             }
4888             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4889                 message[1] = BOARD_RGHT   - 1 - j + '1';
4890                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4891             }
4892             SendToProgram(message, cps);
4893           }
4894         }
4895       }
4896     
4897       SendToProgram("c\n", cps);
4898       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4899         bp = &boards[moveNum][i][BOARD_LEFT];
4900         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4901           if (((int) *bp != (int) EmptySquare)
4902               && ((int) *bp >= (int) BlackPawn)) {
4903             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4904                     AAA + j, ONE + i);
4905             if(message[0] == '+' || message[0] == '~') {
4906                 sprintf(message, "%c%c%c+\n",
4907                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4908                         AAA + j, ONE + i);
4909             }
4910             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4911                 message[1] = BOARD_RGHT   - 1 - j + '1';
4912                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4913             }
4914             SendToProgram(message, cps);
4915           }
4916         }
4917       }
4918     
4919       SendToProgram(".\n", cps);
4920     }
4921     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4922 }
4923
4924 int
4925 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4926 {
4927     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4928     /* [HGM] add Shogi promotions */
4929     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4930     ChessSquare piece;
4931     ChessMove moveType;
4932     Boolean premove;
4933
4934     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4935     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4936
4937     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4938       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4939         return FALSE;
4940
4941     piece = boards[currentMove][fromY][fromX];
4942     if(gameInfo.variant == VariantShogi) {
4943         promotionZoneSize = 3;
4944         highestPromotingPiece = (int)WhiteFerz;
4945     }
4946
4947     // next weed out all moves that do not touch the promotion zone at all
4948     if((int)piece >= BlackPawn) {
4949         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4950              return FALSE;
4951         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4952     } else {
4953         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4954            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4955     }
4956
4957     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4958
4959     // weed out mandatory Shogi promotions
4960     if(gameInfo.variant == VariantShogi) {
4961         if(piece >= BlackPawn) {
4962             if(toY == 0 && piece == BlackPawn ||
4963                toY == 0 && piece == BlackQueen ||
4964                toY <= 1 && piece == BlackKnight) {
4965                 *promoChoice = '+';
4966                 return FALSE;
4967             }
4968         } else {
4969             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4970                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4971                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4972                 *promoChoice = '+';
4973                 return FALSE;
4974             }
4975         }
4976     }
4977
4978     // weed out obviously illegal Pawn moves
4979     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4980         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4981         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4982         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4983         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4984         // note we are not allowed to test for valid (non-)capture, due to premove
4985     }
4986
4987     // we either have a choice what to promote to, or (in Shogi) whether to promote
4988     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4989         *promoChoice = PieceToChar(BlackFerz);  // no choice
4990         return FALSE;
4991     }
4992     if(appData.alwaysPromoteToQueen) { // predetermined
4993         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4994              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4995         else *promoChoice = PieceToChar(BlackQueen);
4996         return FALSE;
4997     }
4998
4999     // suppress promotion popup on illegal moves that are not premoves
5000     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5001               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5002     if(appData.testLegality && !premove) {
5003         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5004                         epStatus[currentMove], castlingRights[currentMove],
5005                         fromY, fromX, toY, toX, NULLCHAR);
5006         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5007            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5008             return FALSE;
5009     }
5010
5011     return TRUE;
5012 }
5013
5014 int
5015 InPalace(row, column)
5016      int row, column;
5017 {   /* [HGM] for Xiangqi */
5018     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5019          column < (BOARD_WIDTH + 4)/2 &&
5020          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5021     return FALSE;
5022 }
5023
5024 int
5025 PieceForSquare (x, y)
5026      int x;
5027      int y;
5028 {
5029   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5030      return -1;
5031   else
5032      return boards[currentMove][y][x];
5033 }
5034
5035 int
5036 OKToStartUserMove(x, y)
5037      int x, y;
5038 {
5039     ChessSquare from_piece;
5040     int white_piece;
5041
5042     if (matchMode) return FALSE;
5043     if (gameMode == EditPosition) return TRUE;
5044
5045     if (x >= 0 && y >= 0)
5046       from_piece = boards[currentMove][y][x];
5047     else
5048       from_piece = EmptySquare;
5049
5050     if (from_piece == EmptySquare) return FALSE;
5051
5052     white_piece = (int)from_piece >= (int)WhitePawn &&
5053       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5054
5055     switch (gameMode) {
5056       case PlayFromGameFile:
5057       case AnalyzeFile:
5058       case TwoMachinesPlay:
5059       case EndOfGame:
5060         return FALSE;
5061
5062       case IcsObserving:
5063       case IcsIdle:
5064         return FALSE;
5065
5066       case MachinePlaysWhite:
5067       case IcsPlayingBlack:
5068         if (appData.zippyPlay) return FALSE;
5069         if (white_piece) {
5070             DisplayMoveError(_("You are playing Black"));
5071             return FALSE;
5072         }
5073         break;
5074
5075       case MachinePlaysBlack:
5076       case IcsPlayingWhite:
5077         if (appData.zippyPlay) return FALSE;
5078         if (!white_piece) {
5079             DisplayMoveError(_("You are playing White"));
5080             return FALSE;
5081         }
5082         break;
5083
5084       case EditGame:
5085         if (!white_piece && WhiteOnMove(currentMove)) {
5086             DisplayMoveError(_("It is White's turn"));
5087             return FALSE;
5088         }           
5089         if (white_piece && !WhiteOnMove(currentMove)) {
5090             DisplayMoveError(_("It is Black's turn"));
5091             return FALSE;
5092         }           
5093         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5094             /* Editing correspondence game history */
5095             /* Could disallow this or prompt for confirmation */
5096             cmailOldMove = -1;
5097         }
5098         if (currentMove < forwardMostMove) {
5099             /* Discarding moves */
5100             /* Could prompt for confirmation here,
5101                but I don't think that's such a good idea */
5102             forwardMostMove = currentMove;
5103         }
5104         break;
5105
5106       case BeginningOfGame:
5107         if (appData.icsActive) return FALSE;
5108         if (!appData.noChessProgram) {
5109             if (!white_piece) {
5110                 DisplayMoveError(_("You are playing White"));
5111                 return FALSE;
5112             }
5113         }
5114         break;
5115         
5116       case Training:
5117         if (!white_piece && WhiteOnMove(currentMove)) {
5118             DisplayMoveError(_("It is White's turn"));
5119             return FALSE;
5120         }           
5121         if (white_piece && !WhiteOnMove(currentMove)) {
5122             DisplayMoveError(_("It is Black's turn"));
5123             return FALSE;
5124         }           
5125         break;
5126
5127       default:
5128       case IcsExamining:
5129         break;
5130     }
5131     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5132         && gameMode != AnalyzeFile && gameMode != Training) {
5133         DisplayMoveError(_("Displayed position is not current"));
5134         return FALSE;
5135     }
5136     return TRUE;
5137 }
5138
5139 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5140 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5141 int lastLoadGameUseList = FALSE;
5142 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5143 ChessMove lastLoadGameStart = (ChessMove) 0;
5144
5145 ChessMove
5146 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5147      int fromX, fromY, toX, toY;
5148      int promoChar;
5149      Boolean captureOwn;
5150 {
5151     ChessMove moveType;
5152     ChessSquare pdown, pup;
5153
5154     /* Check if the user is playing in turn.  This is complicated because we
5155        let the user "pick up" a piece before it is his turn.  So the piece he
5156        tried to pick up may have been captured by the time he puts it down!
5157        Therefore we use the color the user is supposed to be playing in this
5158        test, not the color of the piece that is currently on the starting
5159        square---except in EditGame mode, where the user is playing both
5160        sides; fortunately there the capture race can't happen.  (It can
5161        now happen in IcsExamining mode, but that's just too bad.  The user
5162        will get a somewhat confusing message in that case.)
5163        */
5164
5165     switch (gameMode) {
5166       case PlayFromGameFile:
5167       case AnalyzeFile:
5168       case TwoMachinesPlay:
5169       case EndOfGame:
5170       case IcsObserving:
5171       case IcsIdle:
5172         /* We switched into a game mode where moves are not accepted,
5173            perhaps while the mouse button was down. */
5174         return ImpossibleMove;
5175
5176       case MachinePlaysWhite:
5177         /* User is moving for Black */
5178         if (WhiteOnMove(currentMove)) {
5179             DisplayMoveError(_("It is White's turn"));
5180             return ImpossibleMove;
5181         }
5182         break;
5183
5184       case MachinePlaysBlack:
5185         /* User is moving for White */
5186         if (!WhiteOnMove(currentMove)) {
5187             DisplayMoveError(_("It is Black's turn"));
5188             return ImpossibleMove;
5189         }
5190         break;
5191
5192       case EditGame:
5193       case IcsExamining:
5194       case BeginningOfGame:
5195       case AnalyzeMode:
5196       case Training:
5197         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5198             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5199             /* User is moving for Black */
5200             if (WhiteOnMove(currentMove)) {
5201                 DisplayMoveError(_("It is White's turn"));
5202                 return ImpossibleMove;
5203             }
5204         } else {
5205             /* User is moving for White */
5206             if (!WhiteOnMove(currentMove)) {
5207                 DisplayMoveError(_("It is Black's turn"));
5208                 return ImpossibleMove;
5209             }
5210         }
5211         break;
5212
5213       case IcsPlayingBlack:
5214         /* User is moving for Black */
5215         if (WhiteOnMove(currentMove)) {
5216             if (!appData.premove) {
5217                 DisplayMoveError(_("It is White's turn"));
5218             } else if (toX >= 0 && toY >= 0) {
5219                 premoveToX = toX;
5220                 premoveToY = toY;
5221                 premoveFromX = fromX;
5222                 premoveFromY = fromY;
5223                 premovePromoChar = promoChar;
5224                 gotPremove = 1;
5225                 if (appData.debugMode) 
5226                     fprintf(debugFP, "Got premove: fromX %d,"
5227                             "fromY %d, toX %d, toY %d\n",
5228                             fromX, fromY, toX, toY);
5229             }
5230             return ImpossibleMove;
5231         }
5232         break;
5233
5234       case IcsPlayingWhite:
5235         /* User is moving for White */
5236         if (!WhiteOnMove(currentMove)) {
5237             if (!appData.premove) {
5238                 DisplayMoveError(_("It is Black's turn"));
5239             } else if (toX >= 0 && toY >= 0) {
5240                 premoveToX = toX;
5241                 premoveToY = toY;
5242                 premoveFromX = fromX;
5243                 premoveFromY = fromY;
5244                 premovePromoChar = promoChar;
5245                 gotPremove = 1;
5246                 if (appData.debugMode) 
5247                     fprintf(debugFP, "Got premove: fromX %d,"
5248                             "fromY %d, toX %d, toY %d\n",
5249                             fromX, fromY, toX, toY);
5250             }
5251             return ImpossibleMove;
5252         }
5253         break;
5254
5255       default:
5256         break;
5257
5258       case EditPosition:
5259         /* EditPosition, empty square, or different color piece;
5260            click-click move is possible */
5261         if (toX == -2 || toY == -2) {
5262             boards[0][fromY][fromX] = EmptySquare;
5263             return AmbiguousMove;
5264         } else if (toX >= 0 && toY >= 0) {
5265             boards[0][toY][toX] = boards[0][fromY][fromX];
5266             boards[0][fromY][fromX] = EmptySquare;
5267             return AmbiguousMove;
5268         }
5269         return ImpossibleMove;
5270     }
5271
5272     if(toX < 0 || toY < 0) return ImpossibleMove;
5273     pdown = boards[currentMove][fromY][fromX];
5274     pup = boards[currentMove][toY][toX];
5275
5276     /* [HGM] If move started in holdings, it means a drop */
5277     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5278          if( pup != EmptySquare ) return ImpossibleMove;
5279          if(appData.testLegality) {
5280              /* it would be more logical if LegalityTest() also figured out
5281               * which drops are legal. For now we forbid pawns on back rank.
5282               * Shogi is on its own here...
5283               */
5284              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5285                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5286                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5287          }
5288          return WhiteDrop; /* Not needed to specify white or black yet */
5289     }
5290
5291     userOfferedDraw = FALSE;
5292         
5293     /* [HGM] always test for legality, to get promotion info */
5294     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5295                           epStatus[currentMove], castlingRights[currentMove],
5296                                          fromY, fromX, toY, toX, promoChar);
5297     /* [HGM] but possibly ignore an IllegalMove result */
5298     if (appData.testLegality) {
5299         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5300             DisplayMoveError(_("Illegal move"));
5301             return ImpossibleMove;
5302         }
5303     }
5304 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5305     return moveType;
5306     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5307        function is made into one that returns an OK move type if FinishMove
5308        should be called. This to give the calling driver routine the
5309        opportunity to finish the userMove input with a promotion popup,
5310        without bothering the user with this for invalid or illegal moves */
5311
5312 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5313 }
5314
5315 /* Common tail of UserMoveEvent and DropMenuEvent */
5316 int
5317 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5318      ChessMove moveType;
5319      int fromX, fromY, toX, toY;
5320      /*char*/int promoChar;
5321 {
5322     char *bookHit = 0;
5323 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5324     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5325         // [HGM] superchess: suppress promotions to non-available piece
5326         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5327         if(WhiteOnMove(currentMove)) {
5328             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5329         } else {
5330             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5331         }
5332     }
5333
5334     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5335        move type in caller when we know the move is a legal promotion */
5336     if(moveType == NormalMove && promoChar)
5337         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5338 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5339     /* [HGM] convert drag-and-drop piece drops to standard form */
5340     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5341          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5342            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5343                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5344 //         fromX = boards[currentMove][fromY][fromX];
5345            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5346            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5347            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5348            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5349          fromY = DROP_RANK;
5350     }
5351
5352     /* [HGM] <popupFix> The following if has been moved here from
5353        UserMoveEvent(). Because it seemed to belon here (why not allow
5354        piece drops in training games?), and because it can only be
5355        performed after it is known to what we promote. */
5356     if (gameMode == Training) {
5357       /* compare the move played on the board to the next move in the
5358        * game. If they match, display the move and the opponent's response. 
5359        * If they don't match, display an error message.
5360        */
5361       int saveAnimate;
5362       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5363       CopyBoard(testBoard, boards[currentMove]);
5364       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5365
5366       if (CompareBoards(testBoard, boards[currentMove+1])) {
5367         ForwardInner(currentMove+1);
5368
5369         /* Autoplay the opponent's response.
5370          * if appData.animate was TRUE when Training mode was entered,
5371          * the response will be animated.
5372          */
5373         saveAnimate = appData.animate;
5374         appData.animate = animateTraining;
5375         ForwardInner(currentMove+1);
5376         appData.animate = saveAnimate;
5377
5378         /* check for the end of the game */
5379         if (currentMove >= forwardMostMove) {
5380           gameMode = PlayFromGameFile;
5381           ModeHighlight();
5382           SetTrainingModeOff();
5383           DisplayInformation(_("End of game"));
5384         }
5385       } else {
5386         DisplayError(_("Incorrect move"), 0);
5387       }
5388       return 1;
5389     }
5390
5391   /* Ok, now we know that the move is good, so we can kill
5392      the previous line in Analysis Mode */
5393   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5394     forwardMostMove = currentMove;
5395   }
5396
5397   /* If we need the chess program but it's dead, restart it */
5398   ResurrectChessProgram();
5399
5400   /* A user move restarts a paused game*/
5401   if (pausing)
5402     PauseEvent();
5403
5404   thinkOutput[0] = NULLCHAR;
5405
5406   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5407
5408   if (gameMode == BeginningOfGame) {
5409     if (appData.noChessProgram) {
5410       gameMode = EditGame;
5411       SetGameInfo();
5412     } else {
5413       char buf[MSG_SIZ];
5414       gameMode = MachinePlaysBlack;
5415       StartClocks();
5416       SetGameInfo();
5417       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5418       DisplayTitle(buf);
5419       if (first.sendName) {
5420         sprintf(buf, "name %s\n", gameInfo.white);
5421         SendToProgram(buf, &first);
5422       }
5423       StartClocks();
5424     }
5425     ModeHighlight();
5426   }
5427 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5428   /* Relay move to ICS or chess engine */
5429   if (appData.icsActive) {
5430     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5431         gameMode == IcsExamining) {
5432       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5433       ics_user_moved = 1;
5434     }
5435   } else {
5436     if (first.sendTime && (gameMode == BeginningOfGame ||
5437                            gameMode == MachinePlaysWhite ||
5438                            gameMode == MachinePlaysBlack)) {
5439       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5440     }
5441     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5442          // [HGM] book: if program might be playing, let it use book
5443         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5444         first.maybeThinking = TRUE;
5445     } else SendMoveToProgram(forwardMostMove-1, &first);
5446     if (currentMove == cmailOldMove + 1) {
5447       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5448     }
5449   }
5450
5451   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5452
5453   switch (gameMode) {
5454   case EditGame:
5455     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5456                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5457     case MT_NONE:
5458     case MT_CHECK:
5459       break;
5460     case MT_CHECKMATE:
5461     case MT_STAINMATE:
5462       if (WhiteOnMove(currentMove)) {
5463         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5464       } else {
5465         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5466       }
5467       break;
5468     case MT_STALEMATE:
5469       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5470       break;
5471     }
5472     break;
5473     
5474   case MachinePlaysBlack:
5475   case MachinePlaysWhite:
5476     /* disable certain menu options while machine is thinking */
5477     SetMachineThinkingEnables();
5478     break;
5479
5480   default:
5481     break;
5482   }
5483
5484   if(bookHit) { // [HGM] book: simulate book reply
5485         static char bookMove[MSG_SIZ]; // a bit generous?
5486
5487         programStats.nodes = programStats.depth = programStats.time = 
5488         programStats.score = programStats.got_only_move = 0;
5489         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5490
5491         strcpy(bookMove, "move ");
5492         strcat(bookMove, bookHit);
5493         HandleMachineMove(bookMove, &first);
5494   }
5495   return 1;
5496 }
5497
5498 void
5499 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5500      int fromX, fromY, toX, toY;
5501      int promoChar;
5502 {
5503     /* [HGM] This routine was added to allow calling of its two logical
5504        parts from other modules in the old way. Before, UserMoveEvent()
5505        automatically called FinishMove() if the move was OK, and returned
5506        otherwise. I separated the two, in order to make it possible to
5507        slip a promotion popup in between. But that it always needs two
5508        calls, to the first part, (now called UserMoveTest() ), and to
5509        FinishMove if the first part succeeded. Calls that do not need
5510        to do anything in between, can call this routine the old way. 
5511     */
5512     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5513 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5514     if(moveType == AmbiguousMove)
5515         DrawPosition(FALSE, boards[currentMove]);
5516     else if(moveType != ImpossibleMove && moveType != Comment)
5517         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5518 }
5519
5520 void LeftClick(ClickType clickType, int xPix, int yPix)
5521 {
5522     int x, y;
5523     Boolean saveAnimate;
5524     static int second = 0, promotionChoice = 0;
5525     char promoChoice = NULLCHAR;
5526
5527     if (clickType == Press) ErrorPopDown();
5528
5529     x = EventToSquare(xPix, BOARD_WIDTH);
5530     y = EventToSquare(yPix, BOARD_HEIGHT);
5531     if (!flipView && y >= 0) {
5532         y = BOARD_HEIGHT - 1 - y;
5533     }
5534     if (flipView && x >= 0) {
5535         x = BOARD_WIDTH - 1 - x;
5536     }
5537
5538     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5539         if(clickType == Release) return; // ignore upclick of click-click destination
5540         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5541         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5542         if(gameInfo.holdingsWidth && 
5543                 (WhiteOnMove(currentMove) 
5544                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5545                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5546             // click in right holdings, for determining promotion piece
5547             ChessSquare p = boards[currentMove][y][x];
5548             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5549             if(p != EmptySquare) {
5550                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5551                 fromX = fromY = -1;
5552                 return;
5553             }
5554         }
5555         DrawPosition(FALSE, boards[currentMove]);
5556         return;
5557     }
5558
5559     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5560     if(clickType == Press
5561             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5562               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5563               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5564         return;
5565
5566     if (fromX == -1) {
5567         if (clickType == Press) {
5568             /* First square */
5569             if (OKToStartUserMove(x, y)) {
5570                 fromX = x;
5571                 fromY = y;
5572                 second = 0;
5573                 DragPieceBegin(xPix, yPix);
5574                 if (appData.highlightDragging) {
5575                     SetHighlights(x, y, -1, -1);
5576                 }
5577             }
5578         }
5579         return;
5580     }
5581
5582     /* fromX != -1 */
5583     if (clickType == Press && gameMode != EditPosition) {
5584         ChessSquare fromP;
5585         ChessSquare toP;
5586         int frc;
5587
5588         // ignore off-board to clicks
5589         if(y < 0 || x < 0) return;
5590
5591         /* Check if clicking again on the same color piece */
5592         fromP = boards[currentMove][fromY][fromX];
5593         toP = boards[currentMove][y][x];
5594         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5595         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5596              WhitePawn <= toP && toP <= WhiteKing &&
5597              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5598              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5599             (BlackPawn <= fromP && fromP <= BlackKing && 
5600              BlackPawn <= toP && toP <= BlackKing &&
5601              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5602              !(fromP == BlackKing && toP == BlackRook && frc))) {
5603             /* Clicked again on same color piece -- changed his mind */
5604             second = (x == fromX && y == fromY);
5605             if (appData.highlightDragging) {
5606                 SetHighlights(x, y, -1, -1);
5607             } else {
5608                 ClearHighlights();
5609             }
5610             if (OKToStartUserMove(x, y)) {
5611                 fromX = x;
5612                 fromY = y;
5613                 DragPieceBegin(xPix, yPix);
5614             }
5615             return;
5616         }
5617         // ignore clicks on holdings
5618         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5619     }
5620
5621     if (clickType == Release && x == fromX && y == fromY) {
5622         DragPieceEnd(xPix, yPix);
5623         if (appData.animateDragging) {
5624             /* Undo animation damage if any */
5625             DrawPosition(FALSE, NULL);
5626         }
5627         if (second) {
5628             /* Second up/down in same square; just abort move */
5629             second = 0;
5630             fromX = fromY = -1;
5631             ClearHighlights();
5632             gotPremove = 0;
5633             ClearPremoveHighlights();
5634         } else {
5635             /* First upclick in same square; start click-click mode */
5636             SetHighlights(x, y, -1, -1);
5637         }
5638         return;
5639     }
5640
5641     /* we now have a different from- and (possibly off-board) to-square */
5642     /* Completed move */
5643     toX = x;
5644     toY = y;
5645     saveAnimate = appData.animate;
5646     if (clickType == Press) {
5647         /* Finish clickclick move */
5648         if (appData.animate || appData.highlightLastMove) {
5649             SetHighlights(fromX, fromY, toX, toY);
5650         } else {
5651             ClearHighlights();
5652         }
5653     } else {
5654         /* Finish drag move */
5655         if (appData.highlightLastMove) {
5656             SetHighlights(fromX, fromY, toX, toY);
5657         } else {
5658             ClearHighlights();
5659         }
5660         DragPieceEnd(xPix, yPix);
5661         /* Don't animate move and drag both */
5662         appData.animate = FALSE;
5663     }
5664
5665     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5666     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5667         ClearHighlights();
5668         fromX = fromY = -1;
5669         return;
5670     }
5671
5672     // off-board moves should not be highlighted
5673     if(x < 0 || x < 0) ClearHighlights();
5674
5675     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5676         SetHighlights(fromX, fromY, toX, toY);
5677         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5678             // [HGM] super: promotion to captured piece selected from holdings
5679             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5680             promotionChoice = TRUE;
5681             // kludge follows to temporarily execute move on display, without promoting yet
5682             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5683             boards[currentMove][toY][toX] = p;
5684             DrawPosition(FALSE, boards[currentMove]);
5685             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5686             boards[currentMove][toY][toX] = q;
5687             DisplayMessage("Click in holdings to choose piece", "");
5688             return;
5689         }
5690         PromotionPopUp();
5691     } else {
5692         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5693         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5694         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5695         fromX = fromY = -1;
5696     }
5697     appData.animate = saveAnimate;
5698     if (appData.animate || appData.animateDragging) {
5699         /* Undo animation damage if needed */
5700         DrawPosition(FALSE, NULL);
5701     }
5702 }
5703
5704 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5705 {
5706 //    char * hint = lastHint;
5707     FrontEndProgramStats stats;
5708
5709     stats.which = cps == &first ? 0 : 1;
5710     stats.depth = cpstats->depth;
5711     stats.nodes = cpstats->nodes;
5712     stats.score = cpstats->score;
5713     stats.time = cpstats->time;
5714     stats.pv = cpstats->movelist;
5715     stats.hint = lastHint;
5716     stats.an_move_index = 0;
5717     stats.an_move_count = 0;
5718
5719     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5720         stats.hint = cpstats->move_name;
5721         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5722         stats.an_move_count = cpstats->nr_moves;
5723     }
5724
5725     SetProgramStats( &stats );
5726 }
5727
5728 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5729 {   // [HGM] book: this routine intercepts moves to simulate book replies
5730     char *bookHit = NULL;
5731
5732     //first determine if the incoming move brings opponent into his book
5733     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5734         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5735     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5736     if(bookHit != NULL && !cps->bookSuspend) {
5737         // make sure opponent is not going to reply after receiving move to book position
5738         SendToProgram("force\n", cps);
5739         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5740     }
5741     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5742     // now arrange restart after book miss
5743     if(bookHit) {
5744         // after a book hit we never send 'go', and the code after the call to this routine
5745         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5746         char buf[MSG_SIZ];
5747         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5748         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5749         SendToProgram(buf, cps);
5750         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5751     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5752         SendToProgram("go\n", cps);
5753         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5754     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5755         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5756             SendToProgram("go\n", cps); 
5757         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5758     }
5759     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5760 }
5761
5762 char *savedMessage;
5763 ChessProgramState *savedState;
5764 void DeferredBookMove(void)
5765 {
5766         if(savedState->lastPing != savedState->lastPong)
5767                     ScheduleDelayedEvent(DeferredBookMove, 10);
5768         else
5769         HandleMachineMove(savedMessage, savedState);
5770 }
5771
5772 void
5773 HandleMachineMove(message, cps)
5774      char *message;
5775      ChessProgramState *cps;
5776 {
5777     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5778     char realname[MSG_SIZ];
5779     int fromX, fromY, toX, toY;
5780     ChessMove moveType;
5781     char promoChar;
5782     char *p;
5783     int machineWhite;
5784     char *bookHit;
5785
5786 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5787     /*
5788      * Kludge to ignore BEL characters
5789      */
5790     while (*message == '\007') message++;
5791
5792     /*
5793      * [HGM] engine debug message: ignore lines starting with '#' character
5794      */
5795     if(cps->debug && *message == '#') return;
5796
5797     /*
5798      * Look for book output
5799      */
5800     if (cps == &first && bookRequested) {
5801         if (message[0] == '\t' || message[0] == ' ') {
5802             /* Part of the book output is here; append it */
5803             strcat(bookOutput, message);
5804             strcat(bookOutput, "  \n");
5805             return;
5806         } else if (bookOutput[0] != NULLCHAR) {
5807             /* All of book output has arrived; display it */
5808             char *p = bookOutput;
5809             while (*p != NULLCHAR) {
5810                 if (*p == '\t') *p = ' ';
5811                 p++;
5812             }
5813             DisplayInformation(bookOutput);
5814             bookRequested = FALSE;
5815             /* Fall through to parse the current output */
5816         }
5817     }
5818
5819     /*
5820      * Look for machine move.
5821      */
5822     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5823         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5824     {
5825         /* This method is only useful on engines that support ping */
5826         if (cps->lastPing != cps->lastPong) {
5827           if (gameMode == BeginningOfGame) {
5828             /* Extra move from before last new; ignore */
5829             if (appData.debugMode) {
5830                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5831             }
5832           } else {
5833             if (appData.debugMode) {
5834                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5835                         cps->which, gameMode);
5836             }
5837
5838             SendToProgram("undo\n", cps);
5839           }
5840           return;
5841         }
5842
5843         switch (gameMode) {
5844           case BeginningOfGame:
5845             /* Extra move from before last reset; ignore */
5846             if (appData.debugMode) {
5847                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5848             }
5849             return;
5850
5851           case EndOfGame:
5852           case IcsIdle:
5853           default:
5854             /* Extra move after we tried to stop.  The mode test is
5855                not a reliable way of detecting this problem, but it's
5856                the best we can do on engines that don't support ping.
5857             */
5858             if (appData.debugMode) {
5859                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5860                         cps->which, gameMode);
5861             }
5862             SendToProgram("undo\n", cps);
5863             return;
5864
5865           case MachinePlaysWhite:
5866           case IcsPlayingWhite:
5867             machineWhite = TRUE;
5868             break;
5869
5870           case MachinePlaysBlack:
5871           case IcsPlayingBlack:
5872             machineWhite = FALSE;
5873             break;
5874
5875           case TwoMachinesPlay:
5876             machineWhite = (cps->twoMachinesColor[0] == 'w');
5877             break;
5878         }
5879         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5880             if (appData.debugMode) {
5881                 fprintf(debugFP,
5882                         "Ignoring move out of turn by %s, gameMode %d"
5883                         ", forwardMost %d\n",
5884                         cps->which, gameMode, forwardMostMove);
5885             }
5886             return;
5887         }
5888
5889     if (appData.debugMode) { int f = forwardMostMove;
5890         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5891                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5892     }
5893         if(cps->alphaRank) AlphaRank(machineMove, 4);
5894         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5895                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5896             /* Machine move could not be parsed; ignore it. */
5897             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5898                     machineMove, cps->which);
5899             DisplayError(buf1, 0);
5900             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5901                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5902             if (gameMode == TwoMachinesPlay) {
5903               GameEnds(machineWhite ? BlackWins : WhiteWins,
5904                        buf1, GE_XBOARD);
5905             }
5906             return;
5907         }
5908
5909         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5910         /* So we have to redo legality test with true e.p. status here,  */
5911         /* to make sure an illegal e.p. capture does not slip through,   */
5912         /* to cause a forfeit on a justified illegal-move complaint      */
5913         /* of the opponent.                                              */
5914         if( gameMode==TwoMachinesPlay && appData.testLegality
5915             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5916                                                               ) {
5917            ChessMove moveType;
5918            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5919                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5920                              fromY, fromX, toY, toX, promoChar);
5921             if (appData.debugMode) {
5922                 int i;
5923                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5924                     castlingRights[forwardMostMove][i], castlingRank[i]);
5925                 fprintf(debugFP, "castling rights\n");
5926             }
5927             if(moveType == IllegalMove) {
5928                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5929                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5930                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5931                            buf1, GE_XBOARD);
5932                 return;
5933            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5934            /* [HGM] Kludge to handle engines that send FRC-style castling
5935               when they shouldn't (like TSCP-Gothic) */
5936            switch(moveType) {
5937              case WhiteASideCastleFR:
5938              case BlackASideCastleFR:
5939                toX+=2;
5940                currentMoveString[2]++;
5941                break;
5942              case WhiteHSideCastleFR:
5943              case BlackHSideCastleFR:
5944                toX--;
5945                currentMoveString[2]--;
5946                break;
5947              default: ; // nothing to do, but suppresses warning of pedantic compilers
5948            }
5949         }
5950         hintRequested = FALSE;
5951         lastHint[0] = NULLCHAR;
5952         bookRequested = FALSE;
5953         /* Program may be pondering now */
5954         cps->maybeThinking = TRUE;
5955         if (cps->sendTime == 2) cps->sendTime = 1;
5956         if (cps->offeredDraw) cps->offeredDraw--;
5957
5958 #if ZIPPY
5959         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5960             first.initDone) {
5961           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5962           ics_user_moved = 1;
5963           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5964                 char buf[3*MSG_SIZ];
5965
5966                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5967                         programStats.score / 100.,
5968                         programStats.depth,
5969                         programStats.time / 100.,
5970                         (unsigned int)programStats.nodes,
5971                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5972                         programStats.movelist);
5973                 SendToICS(buf);
5974 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5975           }
5976         }
5977 #endif
5978         /* currentMoveString is set as a side-effect of ParseOneMove */
5979         strcpy(machineMove, currentMoveString);
5980         strcat(machineMove, "\n");
5981         strcpy(moveList[forwardMostMove], machineMove);
5982
5983         /* [AS] Save move info and clear stats for next move */
5984         pvInfoList[ forwardMostMove ].score = programStats.score;
5985         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5986         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5987         ClearProgramStats();
5988         thinkOutput[0] = NULLCHAR;
5989         hiddenThinkOutputState = 0;
5990
5991         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5992
5993         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5994         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5995             int count = 0;
5996
5997             while( count < adjudicateLossPlies ) {
5998                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5999
6000                 if( count & 1 ) {
6001                     score = -score; /* Flip score for winning side */
6002                 }
6003
6004                 if( score > adjudicateLossThreshold ) {
6005                     break;
6006                 }
6007
6008                 count++;
6009             }
6010
6011             if( count >= adjudicateLossPlies ) {
6012                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6013
6014                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6015                     "Xboard adjudication", 
6016                     GE_XBOARD );
6017
6018                 return;
6019             }
6020         }
6021
6022         if( gameMode == TwoMachinesPlay ) {
6023           // [HGM] some adjudications useful with buggy engines
6024             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6025           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6026
6027
6028             if( appData.testLegality )
6029             {   /* [HGM] Some more adjudications for obstinate engines */
6030                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6031                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6032                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6033                 static int moveCount = 6;
6034                 ChessMove result;
6035                 char *reason = NULL;
6036
6037                 /* Count what is on board. */
6038                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6039                 {   ChessSquare p = boards[forwardMostMove][i][j];
6040                     int m=i;
6041
6042                     switch((int) p)
6043                     {   /* count B,N,R and other of each side */
6044                         case WhiteKing:
6045                         case BlackKing:
6046                              NrK++; break; // [HGM] atomic: count Kings
6047                         case WhiteKnight:
6048                              NrWN++; break;
6049                         case WhiteBishop:
6050                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6051                              bishopsColor |= 1 << ((i^j)&1);
6052                              NrWB++; break;
6053                         case BlackKnight:
6054                              NrBN++; break;
6055                         case BlackBishop:
6056                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6057                              bishopsColor |= 1 << ((i^j)&1);
6058                              NrBB++; break;
6059                         case WhiteRook:
6060                              NrWR++; break;
6061                         case BlackRook:
6062                              NrBR++; break;
6063                         case WhiteQueen:
6064                              NrWQ++; break;
6065                         case BlackQueen:
6066                              NrBQ++; break;
6067                         case EmptySquare: 
6068                              break;
6069                         case BlackPawn:
6070                              m = 7-i;
6071                         case WhitePawn:
6072                              PawnAdvance += m; NrPawns++;
6073                     }
6074                     NrPieces += (p != EmptySquare);
6075                     NrW += ((int)p < (int)BlackPawn);
6076                     if(gameInfo.variant == VariantXiangqi && 
6077                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6078                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6079                         NrW -= ((int)p < (int)BlackPawn);
6080                     }
6081                 }
6082
6083                 /* Some material-based adjudications that have to be made before stalemate test */
6084                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6085                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6086                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6087                      if(appData.checkMates) {
6088                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6089                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6090                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6091                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6092                          return;
6093                      }
6094                 }
6095
6096                 /* Bare King in Shatranj (loses) or Losers (wins) */
6097                 if( NrW == 1 || NrPieces - NrW == 1) {
6098                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6099                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6100                      if(appData.checkMates) {
6101                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6102                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6104                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6105                          return;
6106                      }
6107                   } else
6108                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6109                   {    /* bare King */
6110                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6111                         if(appData.checkMates) {
6112                             /* but only adjudicate if adjudication enabled */
6113                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6114                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6115                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6116                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6117                             return;
6118                         }
6119                   }
6120                 } else bare = 1;
6121
6122
6123             // don't wait for engine to announce game end if we can judge ourselves
6124             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6125                                        castlingRights[forwardMostMove]) ) {
6126               case MT_CHECK:
6127                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6128                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6129                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6130                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6131                             checkCnt++;
6132                         if(checkCnt >= 2) {
6133                             reason = "Xboard adjudication: 3rd check";
6134                             epStatus[forwardMostMove] = EP_CHECKMATE;
6135                             break;
6136                         }
6137                     }
6138                 }
6139               case MT_NONE:
6140               default:
6141                 break;
6142               case MT_STALEMATE:
6143               case MT_STAINMATE:
6144                 reason = "Xboard adjudication: Stalemate";
6145                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6146                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6147                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6148                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6149                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6150                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6151                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6152                                                                         EP_CHECKMATE : EP_WINS);
6153                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6154                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6155                 }
6156                 break;
6157               case MT_CHECKMATE:
6158                 reason = "Xboard adjudication: Checkmate";
6159                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6160                 break;
6161             }
6162
6163                 switch(i = epStatus[forwardMostMove]) {
6164                     case EP_STALEMATE:
6165                         result = GameIsDrawn; break;
6166                     case EP_CHECKMATE:
6167                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6168                     case EP_WINS:
6169                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6170                     default:
6171                         result = (ChessMove) 0;
6172                 }
6173                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6174                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6175                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6176                     GameEnds( result, reason, GE_XBOARD );
6177                     return;
6178                 }
6179
6180                 /* Next absolutely insufficient mating material. */
6181                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6182                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6183                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6184                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6185                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6186
6187                      /* always flag draws, for judging claims */
6188                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6189
6190                      if(appData.materialDraws) {
6191                          /* but only adjudicate them if adjudication enabled */
6192                          SendToProgram("force\n", cps->other); // suppress reply
6193                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6194                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6195                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6196                          return;
6197                      }
6198                 }
6199
6200                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6201                 if(NrPieces == 4 && 
6202                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6203                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6204                    || NrWN==2 || NrBN==2     /* KNNK */
6205                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6206                   ) ) {
6207                      if(--moveCount < 0 && appData.trivialDraws)
6208                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6209                           SendToProgram("force\n", cps->other); // suppress reply
6210                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6211                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6212                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6213                           return;
6214                      }
6215                 } else moveCount = 6;
6216             }
6217           }
6218           
6219           if (appData.debugMode) { int i;
6220             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6221                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6222                     appData.drawRepeats);
6223             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6224               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6225             
6226           }
6227
6228                 /* Check for rep-draws */
6229                 count = 0;
6230                 for(k = forwardMostMove-2;
6231                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6232                         epStatus[k] < EP_UNKNOWN &&
6233                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6234                     k-=2)
6235                 {   int rights=0;
6236                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6237                         /* compare castling rights */
6238                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6239                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6240                                 rights++; /* King lost rights, while rook still had them */
6241                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6242                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6243                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6244                                    rights++; /* but at least one rook lost them */
6245                         }
6246                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6247                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6248                                 rights++; 
6249                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6250                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6251                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6252                                    rights++;
6253                         }
6254                         if( rights == 0 && ++count > appData.drawRepeats-2
6255                             && appData.drawRepeats > 1) {
6256                              /* adjudicate after user-specified nr of repeats */
6257                              SendToProgram("force\n", cps->other); // suppress reply
6258                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6259                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6260                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6261                                 // [HGM] xiangqi: check for forbidden perpetuals
6262                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6263                                 for(m=forwardMostMove; m>k; m-=2) {
6264                                     if(MateTest(boards[m], PosFlags(m), 
6265                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6266                                         ourPerpetual = 0; // the current mover did not always check
6267                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6268                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6269                                         hisPerpetual = 0; // the opponent did not always check
6270                                 }
6271                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6272                                                                         ourPerpetual, hisPerpetual);
6273                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6274                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6275                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6276                                     return;
6277                                 }
6278                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6279                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6280                                 // Now check for perpetual chases
6281                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6282                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6283                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6284                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6285                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6286                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6287                                         return;
6288                                     }
6289                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6290                                         break; // Abort repetition-checking loop.
6291                                 }
6292                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6293                              }
6294                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6295                              return;
6296                         }
6297                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6298                              epStatus[forwardMostMove] = EP_REP_DRAW;
6299                     }
6300                 }
6301
6302                 /* Now we test for 50-move draws. Determine ply count */
6303                 count = forwardMostMove;
6304                 /* look for last irreversble move */
6305                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6306                     count--;
6307                 /* if we hit starting position, add initial plies */
6308                 if( count == backwardMostMove )
6309                     count -= initialRulePlies;
6310                 count = forwardMostMove - count; 
6311                 if( count >= 100)
6312                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6313                          /* this is used to judge if draw claims are legal */
6314                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6315                          SendToProgram("force\n", cps->other); // suppress reply
6316                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6317                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6319                          return;
6320                 }
6321
6322                 /* if draw offer is pending, treat it as a draw claim
6323                  * when draw condition present, to allow engines a way to
6324                  * claim draws before making their move to avoid a race
6325                  * condition occurring after their move
6326                  */
6327                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6328                          char *p = NULL;
6329                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6330                              p = "Draw claim: 50-move rule";
6331                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6332                              p = "Draw claim: 3-fold repetition";
6333                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6334                              p = "Draw claim: insufficient mating material";
6335                          if( p != NULL ) {
6336                              SendToProgram("force\n", cps->other); // suppress reply
6337                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6338                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6339                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6340                              return;
6341                          }
6342                 }
6343
6344
6345                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6346                     SendToProgram("force\n", cps->other); // suppress reply
6347                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6348                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6349
6350                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6351
6352                     return;
6353                 }
6354         }
6355
6356         bookHit = NULL;
6357         if (gameMode == TwoMachinesPlay) {
6358             /* [HGM] relaying draw offers moved to after reception of move */
6359             /* and interpreting offer as claim if it brings draw condition */
6360             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6361                 SendToProgram("draw\n", cps->other);
6362             }
6363             if (cps->other->sendTime) {
6364                 SendTimeRemaining(cps->other,
6365                                   cps->other->twoMachinesColor[0] == 'w');
6366             }
6367             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6368             if (firstMove && !bookHit) {
6369                 firstMove = FALSE;
6370                 if (cps->other->useColors) {
6371                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6372                 }
6373                 SendToProgram("go\n", cps->other);
6374             }
6375             cps->other->maybeThinking = TRUE;
6376         }
6377
6378         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379         
6380         if (!pausing && appData.ringBellAfterMoves) {
6381             RingBell();
6382         }
6383
6384         /* 
6385          * Reenable menu items that were disabled while
6386          * machine was thinking
6387          */
6388         if (gameMode != TwoMachinesPlay)
6389             SetUserThinkingEnables();
6390
6391         // [HGM] book: after book hit opponent has received move and is now in force mode
6392         // force the book reply into it, and then fake that it outputted this move by jumping
6393         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6394         if(bookHit) {
6395                 static char bookMove[MSG_SIZ]; // a bit generous?
6396
6397                 strcpy(bookMove, "move ");
6398                 strcat(bookMove, bookHit);
6399                 message = bookMove;
6400                 cps = cps->other;
6401                 programStats.nodes = programStats.depth = programStats.time = 
6402                 programStats.score = programStats.got_only_move = 0;
6403                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6404
6405                 if(cps->lastPing != cps->lastPong) {
6406                     savedMessage = message; // args for deferred call
6407                     savedState = cps;
6408                     ScheduleDelayedEvent(DeferredBookMove, 10);
6409                     return;
6410                 }
6411                 goto FakeBookMove;
6412         }
6413
6414         return;
6415     }
6416
6417     /* Set special modes for chess engines.  Later something general
6418      *  could be added here; for now there is just one kludge feature,
6419      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6420      *  when "xboard" is given as an interactive command.
6421      */
6422     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6423         cps->useSigint = FALSE;
6424         cps->useSigterm = FALSE;
6425     }
6426     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6427       ParseFeatures(message+8, cps);
6428       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6429     }
6430
6431     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6432      * want this, I was asked to put it in, and obliged.
6433      */
6434     if (!strncmp(message, "setboard ", 9)) {
6435         Board initial_position; int i;
6436
6437         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6438
6439         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6440             DisplayError(_("Bad FEN received from engine"), 0);
6441             return ;
6442         } else {
6443            Reset(TRUE, FALSE);
6444            CopyBoard(boards[0], initial_position);
6445            initialRulePlies = FENrulePlies;
6446            epStatus[0] = FENepStatus;
6447            for( i=0; i<nrCastlingRights; i++ )
6448                 castlingRights[0][i] = FENcastlingRights[i];
6449            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6450            else gameMode = MachinePlaysBlack;                 
6451            DrawPosition(FALSE, boards[currentMove]);
6452         }
6453         return;
6454     }
6455
6456     /*
6457      * Look for communication commands
6458      */
6459     if (!strncmp(message, "telluser ", 9)) {
6460         DisplayNote(message + 9);
6461         return;
6462     }
6463     if (!strncmp(message, "tellusererror ", 14)) {
6464         DisplayError(message + 14, 0);
6465         return;
6466     }
6467     if (!strncmp(message, "tellopponent ", 13)) {
6468       if (appData.icsActive) {
6469         if (loggedOn) {
6470           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6471           SendToICS(buf1);
6472         }
6473       } else {
6474         DisplayNote(message + 13);
6475       }
6476       return;
6477     }
6478     if (!strncmp(message, "tellothers ", 11)) {
6479       if (appData.icsActive) {
6480         if (loggedOn) {
6481           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6482           SendToICS(buf1);
6483         }
6484       }
6485       return;
6486     }
6487     if (!strncmp(message, "tellall ", 8)) {
6488       if (appData.icsActive) {
6489         if (loggedOn) {
6490           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6491           SendToICS(buf1);
6492         }
6493       } else {
6494         DisplayNote(message + 8);
6495       }
6496       return;
6497     }
6498     if (strncmp(message, "warning", 7) == 0) {
6499         /* Undocumented feature, use tellusererror in new code */
6500         DisplayError(message, 0);
6501         return;
6502     }
6503     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6504         strcpy(realname, cps->tidy);
6505         strcat(realname, " query");
6506         AskQuestion(realname, buf2, buf1, cps->pr);
6507         return;
6508     }
6509     /* Commands from the engine directly to ICS.  We don't allow these to be 
6510      *  sent until we are logged on. Crafty kibitzes have been known to 
6511      *  interfere with the login process.
6512      */
6513     if (loggedOn) {
6514         if (!strncmp(message, "tellics ", 8)) {
6515             SendToICS(message + 8);
6516             SendToICS("\n");
6517             return;
6518         }
6519         if (!strncmp(message, "tellicsnoalias ", 15)) {
6520             SendToICS(ics_prefix);
6521             SendToICS(message + 15);
6522             SendToICS("\n");
6523             return;
6524         }
6525         /* The following are for backward compatibility only */
6526         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6527             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6528             SendToICS(ics_prefix);
6529             SendToICS(message);
6530             SendToICS("\n");
6531             return;
6532         }
6533     }
6534     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6535         return;
6536     }
6537     /*
6538      * If the move is illegal, cancel it and redraw the board.
6539      * Also deal with other error cases.  Matching is rather loose
6540      * here to accommodate engines written before the spec.
6541      */
6542     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6543         strncmp(message, "Error", 5) == 0) {
6544         if (StrStr(message, "name") || 
6545             StrStr(message, "rating") || StrStr(message, "?") ||
6546             StrStr(message, "result") || StrStr(message, "board") ||
6547             StrStr(message, "bk") || StrStr(message, "computer") ||
6548             StrStr(message, "variant") || StrStr(message, "hint") ||
6549             StrStr(message, "random") || StrStr(message, "depth") ||
6550             StrStr(message, "accepted")) {
6551             return;
6552         }
6553         if (StrStr(message, "protover")) {
6554           /* Program is responding to input, so it's apparently done
6555              initializing, and this error message indicates it is
6556              protocol version 1.  So we don't need to wait any longer
6557              for it to initialize and send feature commands. */
6558           FeatureDone(cps, 1);
6559           cps->protocolVersion = 1;
6560           return;
6561         }
6562         cps->maybeThinking = FALSE;
6563
6564         if (StrStr(message, "draw")) {
6565             /* Program doesn't have "draw" command */
6566             cps->sendDrawOffers = 0;
6567             return;
6568         }
6569         if (cps->sendTime != 1 &&
6570             (StrStr(message, "time") || StrStr(message, "otim"))) {
6571           /* Program apparently doesn't have "time" or "otim" command */
6572           cps->sendTime = 0;
6573           return;
6574         }
6575         if (StrStr(message, "analyze")) {
6576             cps->analysisSupport = FALSE;
6577             cps->analyzing = FALSE;
6578             Reset(FALSE, TRUE);
6579             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6580             DisplayError(buf2, 0);
6581             return;
6582         }
6583         if (StrStr(message, "(no matching move)st")) {
6584           /* Special kludge for GNU Chess 4 only */
6585           cps->stKludge = TRUE;
6586           SendTimeControl(cps, movesPerSession, timeControl,
6587                           timeIncrement, appData.searchDepth,
6588                           searchTime);
6589           return;
6590         }
6591         if (StrStr(message, "(no matching move)sd")) {
6592           /* Special kludge for GNU Chess 4 only */
6593           cps->sdKludge = TRUE;
6594           SendTimeControl(cps, movesPerSession, timeControl,
6595                           timeIncrement, appData.searchDepth,
6596                           searchTime);
6597           return;
6598         }
6599         if (!StrStr(message, "llegal")) {
6600             return;
6601         }
6602         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6603             gameMode == IcsIdle) return;
6604         if (forwardMostMove <= backwardMostMove) return;
6605         if (pausing) PauseEvent();
6606       if(appData.forceIllegal) {
6607             // [HGM] illegal: machine refused move; force position after move into it
6608           SendToProgram("force\n", cps);
6609           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6610                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6611                 // when black is to move, while there might be nothing on a2 or black
6612                 // might already have the move. So send the board as if white has the move.
6613                 // But first we must change the stm of the engine, as it refused the last move
6614                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6615                 if(WhiteOnMove(forwardMostMove)) {
6616                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6617                     SendBoard(cps, forwardMostMove); // kludgeless board
6618                 } else {
6619                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6620                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6621                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6622                 }
6623           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6624             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6625                  gameMode == TwoMachinesPlay)
6626               SendToProgram("go\n", cps);
6627             return;
6628       } else
6629         if (gameMode == PlayFromGameFile) {
6630             /* Stop reading this game file */
6631             gameMode = EditGame;
6632             ModeHighlight();
6633         }
6634         currentMove = --forwardMostMove;
6635         DisplayMove(currentMove-1); /* before DisplayMoveError */
6636         SwitchClocks();
6637         DisplayBothClocks();
6638         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6639                 parseList[currentMove], cps->which);
6640         DisplayMoveError(buf1);
6641         DrawPosition(FALSE, boards[currentMove]);
6642
6643         /* [HGM] illegal-move claim should forfeit game when Xboard */
6644         /* only passes fully legal moves                            */
6645         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6646             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6647                                 "False illegal-move claim", GE_XBOARD );
6648         }
6649         return;
6650     }
6651     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6652         /* Program has a broken "time" command that
6653            outputs a string not ending in newline.
6654            Don't use it. */
6655         cps->sendTime = 0;
6656     }
6657     
6658     /*
6659      * If chess program startup fails, exit with an error message.
6660      * Attempts to recover here are futile.
6661      */
6662     if ((StrStr(message, "unknown host") != NULL)
6663         || (StrStr(message, "No remote directory") != NULL)
6664         || (StrStr(message, "not found") != NULL)
6665         || (StrStr(message, "No such file") != NULL)
6666         || (StrStr(message, "can't alloc") != NULL)
6667         || (StrStr(message, "Permission denied") != NULL)) {
6668
6669         cps->maybeThinking = FALSE;
6670         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6671                 cps->which, cps->program, cps->host, message);
6672         RemoveInputSource(cps->isr);
6673         DisplayFatalError(buf1, 0, 1);
6674         return;
6675     }
6676     
6677     /* 
6678      * Look for hint output
6679      */
6680     if (sscanf(message, "Hint: %s", buf1) == 1) {
6681         if (cps == &first && hintRequested) {
6682             hintRequested = FALSE;
6683             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6684                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6685                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6686                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6687                                     fromY, fromX, toY, toX, promoChar, buf1);
6688                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6689                 DisplayInformation(buf2);
6690             } else {
6691                 /* Hint move could not be parsed!? */
6692               snprintf(buf2, sizeof(buf2),
6693                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6694                         buf1, cps->which);
6695                 DisplayError(buf2, 0);
6696             }
6697         } else {
6698             strcpy(lastHint, buf1);
6699         }
6700         return;
6701     }
6702
6703     /*
6704      * Ignore other messages if game is not in progress
6705      */
6706     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6707         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6708
6709     /*
6710      * look for win, lose, draw, or draw offer
6711      */
6712     if (strncmp(message, "1-0", 3) == 0) {
6713         char *p, *q, *r = "";
6714         p = strchr(message, '{');
6715         if (p) {
6716             q = strchr(p, '}');
6717             if (q) {
6718                 *q = NULLCHAR;
6719                 r = p + 1;
6720             }
6721         }
6722         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6723         return;
6724     } else if (strncmp(message, "0-1", 3) == 0) {
6725         char *p, *q, *r = "";
6726         p = strchr(message, '{');
6727         if (p) {
6728             q = strchr(p, '}');
6729             if (q) {
6730                 *q = NULLCHAR;
6731                 r = p + 1;
6732             }
6733         }
6734         /* Kludge for Arasan 4.1 bug */
6735         if (strcmp(r, "Black resigns") == 0) {
6736             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6737             return;
6738         }
6739         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6740         return;
6741     } else if (strncmp(message, "1/2", 3) == 0) {
6742         char *p, *q, *r = "";
6743         p = strchr(message, '{');
6744         if (p) {
6745             q = strchr(p, '}');
6746             if (q) {
6747                 *q = NULLCHAR;
6748                 r = p + 1;
6749             }
6750         }
6751             
6752         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6753         return;
6754
6755     } else if (strncmp(message, "White resign", 12) == 0) {
6756         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6757         return;
6758     } else if (strncmp(message, "Black resign", 12) == 0) {
6759         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6760         return;
6761     } else if (strncmp(message, "White matches", 13) == 0 ||
6762                strncmp(message, "Black matches", 13) == 0   ) {
6763         /* [HGM] ignore GNUShogi noises */
6764         return;
6765     } else if (strncmp(message, "White", 5) == 0 &&
6766                message[5] != '(' &&
6767                StrStr(message, "Black") == NULL) {
6768         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6769         return;
6770     } else if (strncmp(message, "Black", 5) == 0 &&
6771                message[5] != '(') {
6772         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6773         return;
6774     } else if (strcmp(message, "resign") == 0 ||
6775                strcmp(message, "computer resigns") == 0) {
6776         switch (gameMode) {
6777           case MachinePlaysBlack:
6778           case IcsPlayingBlack:
6779             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6780             break;
6781           case MachinePlaysWhite:
6782           case IcsPlayingWhite:
6783             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6784             break;
6785           case TwoMachinesPlay:
6786             if (cps->twoMachinesColor[0] == 'w')
6787               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6788             else
6789               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6790             break;
6791           default:
6792             /* can't happen */
6793             break;
6794         }
6795         return;
6796     } else if (strncmp(message, "opponent mates", 14) == 0) {
6797         switch (gameMode) {
6798           case MachinePlaysBlack:
6799           case IcsPlayingBlack:
6800             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6801             break;
6802           case MachinePlaysWhite:
6803           case IcsPlayingWhite:
6804             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6805             break;
6806           case TwoMachinesPlay:
6807             if (cps->twoMachinesColor[0] == 'w')
6808               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6809             else
6810               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6811             break;
6812           default:
6813             /* can't happen */
6814             break;
6815         }
6816         return;
6817     } else if (strncmp(message, "computer mates", 14) == 0) {
6818         switch (gameMode) {
6819           case MachinePlaysBlack:
6820           case IcsPlayingBlack:
6821             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6822             break;
6823           case MachinePlaysWhite:
6824           case IcsPlayingWhite:
6825             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6826             break;
6827           case TwoMachinesPlay:
6828             if (cps->twoMachinesColor[0] == 'w')
6829               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6830             else
6831               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6832             break;
6833           default:
6834             /* can't happen */
6835             break;
6836         }
6837         return;
6838     } else if (strncmp(message, "checkmate", 9) == 0) {
6839         if (WhiteOnMove(forwardMostMove)) {
6840             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6841         } else {
6842             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6843         }
6844         return;
6845     } else if (strstr(message, "Draw") != NULL ||
6846                strstr(message, "game is a draw") != NULL) {
6847         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6848         return;
6849     } else if (strstr(message, "offer") != NULL &&
6850                strstr(message, "draw") != NULL) {
6851 #if ZIPPY
6852         if (appData.zippyPlay && first.initDone) {
6853             /* Relay offer to ICS */
6854             SendToICS(ics_prefix);
6855             SendToICS("draw\n");
6856         }
6857 #endif
6858         cps->offeredDraw = 2; /* valid until this engine moves twice */
6859         if (gameMode == TwoMachinesPlay) {
6860             if (cps->other->offeredDraw) {
6861                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6862             /* [HGM] in two-machine mode we delay relaying draw offer      */
6863             /* until after we also have move, to see if it is really claim */
6864             }
6865         } else if (gameMode == MachinePlaysWhite ||
6866                    gameMode == MachinePlaysBlack) {
6867           if (userOfferedDraw) {
6868             DisplayInformation(_("Machine accepts your draw offer"));
6869             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6870           } else {
6871             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6872           }
6873         }
6874     }
6875
6876     
6877     /*
6878      * Look for thinking output
6879      */
6880     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6881           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6882                                 ) {
6883         int plylev, mvleft, mvtot, curscore, time;
6884         char mvname[MOVE_LEN];
6885         u64 nodes; // [DM]
6886         char plyext;
6887         int ignore = FALSE;
6888         int prefixHint = FALSE;
6889         mvname[0] = NULLCHAR;
6890
6891         switch (gameMode) {
6892           case MachinePlaysBlack:
6893           case IcsPlayingBlack:
6894             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6895             break;
6896           case MachinePlaysWhite:
6897           case IcsPlayingWhite:
6898             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6899             break;
6900           case AnalyzeMode:
6901           case AnalyzeFile:
6902             break;
6903           case IcsObserving: /* [DM] icsEngineAnalyze */
6904             if (!appData.icsEngineAnalyze) ignore = TRUE;
6905             break;
6906           case TwoMachinesPlay:
6907             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6908                 ignore = TRUE;
6909             }
6910             break;
6911           default:
6912             ignore = TRUE;
6913             break;
6914         }
6915
6916         if (!ignore) {
6917             buf1[0] = NULLCHAR;
6918             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6919                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6920
6921                 if (plyext != ' ' && plyext != '\t') {
6922                     time *= 100;
6923                 }
6924
6925                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6926                 if( cps->scoreIsAbsolute && 
6927                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6928                 {
6929                     curscore = -curscore;
6930                 }
6931
6932
6933                 programStats.depth = plylev;
6934                 programStats.nodes = nodes;
6935                 programStats.time = time;
6936                 programStats.score = curscore;
6937                 programStats.got_only_move = 0;
6938
6939                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6940                         int ticklen;
6941
6942                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6943                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6944                         if(WhiteOnMove(forwardMostMove)) 
6945                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6946                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6947                 }
6948
6949                 /* Buffer overflow protection */
6950                 if (buf1[0] != NULLCHAR) {
6951                     if (strlen(buf1) >= sizeof(programStats.movelist)
6952                         && appData.debugMode) {
6953                         fprintf(debugFP,
6954                                 "PV is too long; using the first %d bytes.\n",
6955                                 sizeof(programStats.movelist) - 1);
6956                     }
6957
6958                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6959                 } else {
6960                     sprintf(programStats.movelist, " no PV\n");
6961                 }
6962
6963                 if (programStats.seen_stat) {
6964                     programStats.ok_to_send = 1;
6965                 }
6966
6967                 if (strchr(programStats.movelist, '(') != NULL) {
6968                     programStats.line_is_book = 1;
6969                     programStats.nr_moves = 0;
6970                     programStats.moves_left = 0;
6971                 } else {
6972                     programStats.line_is_book = 0;
6973                 }
6974
6975                 SendProgramStatsToFrontend( cps, &programStats );
6976
6977                 /* 
6978                     [AS] Protect the thinkOutput buffer from overflow... this
6979                     is only useful if buf1 hasn't overflowed first!
6980                 */
6981                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6982                         plylev, 
6983                         (gameMode == TwoMachinesPlay ?
6984                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6985                         ((double) curscore) / 100.0,
6986                         prefixHint ? lastHint : "",
6987                         prefixHint ? " " : "" );
6988
6989                 if( buf1[0] != NULLCHAR ) {
6990                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6991
6992                     if( strlen(buf1) > max_len ) {
6993                         if( appData.debugMode) {
6994                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6995                         }
6996                         buf1[max_len+1] = '\0';
6997                     }
6998
6999                     strcat( thinkOutput, buf1 );
7000                 }
7001
7002                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7003                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7004                     DisplayMove(currentMove - 1);
7005                 }
7006                 return;
7007
7008             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7009                 /* crafty (9.25+) says "(only move) <move>"
7010                  * if there is only 1 legal move
7011                  */
7012                 sscanf(p, "(only move) %s", buf1);
7013                 sprintf(thinkOutput, "%s (only move)", buf1);
7014                 sprintf(programStats.movelist, "%s (only move)", buf1);
7015                 programStats.depth = 1;
7016                 programStats.nr_moves = 1;
7017                 programStats.moves_left = 1;
7018                 programStats.nodes = 1;
7019                 programStats.time = 1;
7020                 programStats.got_only_move = 1;
7021
7022                 /* Not really, but we also use this member to
7023                    mean "line isn't going to change" (Crafty
7024                    isn't searching, so stats won't change) */
7025                 programStats.line_is_book = 1;
7026
7027                 SendProgramStatsToFrontend( cps, &programStats );
7028                 
7029                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7030                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7031                     DisplayMove(currentMove - 1);
7032                 }
7033                 return;
7034             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7035                               &time, &nodes, &plylev, &mvleft,
7036                               &mvtot, mvname) >= 5) {
7037                 /* The stat01: line is from Crafty (9.29+) in response
7038                    to the "." command */
7039                 programStats.seen_stat = 1;
7040                 cps->maybeThinking = TRUE;
7041
7042                 if (programStats.got_only_move || !appData.periodicUpdates)
7043                   return;
7044
7045                 programStats.depth = plylev;
7046                 programStats.time = time;
7047                 programStats.nodes = nodes;
7048                 programStats.moves_left = mvleft;
7049                 programStats.nr_moves = mvtot;
7050                 strcpy(programStats.move_name, mvname);
7051                 programStats.ok_to_send = 1;
7052                 programStats.movelist[0] = '\0';
7053
7054                 SendProgramStatsToFrontend( cps, &programStats );
7055
7056                 return;
7057
7058             } else if (strncmp(message,"++",2) == 0) {
7059                 /* Crafty 9.29+ outputs this */
7060                 programStats.got_fail = 2;
7061                 return;
7062
7063             } else if (strncmp(message,"--",2) == 0) {
7064                 /* Crafty 9.29+ outputs this */
7065                 programStats.got_fail = 1;
7066                 return;
7067
7068             } else if (thinkOutput[0] != NULLCHAR &&
7069                        strncmp(message, "    ", 4) == 0) {
7070                 unsigned message_len;
7071
7072                 p = message;
7073                 while (*p && *p == ' ') p++;
7074
7075                 message_len = strlen( p );
7076
7077                 /* [AS] Avoid buffer overflow */
7078                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7079                     strcat(thinkOutput, " ");
7080                     strcat(thinkOutput, p);
7081                 }
7082
7083                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7084                     strcat(programStats.movelist, " ");
7085                     strcat(programStats.movelist, p);
7086                 }
7087
7088                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7089                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7090                     DisplayMove(currentMove - 1);
7091                 }
7092                 return;
7093             }
7094         }
7095         else {
7096             buf1[0] = NULLCHAR;
7097
7098             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7099                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7100             {
7101                 ChessProgramStats cpstats;
7102
7103                 if (plyext != ' ' && plyext != '\t') {
7104                     time *= 100;
7105                 }
7106
7107                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7108                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7109                     curscore = -curscore;
7110                 }
7111
7112                 cpstats.depth = plylev;
7113                 cpstats.nodes = nodes;
7114                 cpstats.time = time;
7115                 cpstats.score = curscore;
7116                 cpstats.got_only_move = 0;
7117                 cpstats.movelist[0] = '\0';
7118
7119                 if (buf1[0] != NULLCHAR) {
7120                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7121                 }
7122
7123                 cpstats.ok_to_send = 0;
7124                 cpstats.line_is_book = 0;
7125                 cpstats.nr_moves = 0;
7126                 cpstats.moves_left = 0;
7127
7128                 SendProgramStatsToFrontend( cps, &cpstats );
7129             }
7130         }
7131     }
7132 }
7133
7134
7135 /* Parse a game score from the character string "game", and
7136    record it as the history of the current game.  The game
7137    score is NOT assumed to start from the standard position. 
7138    The display is not updated in any way.
7139    */
7140 void
7141 ParseGameHistory(game)
7142      char *game;
7143 {
7144     ChessMove moveType;
7145     int fromX, fromY, toX, toY, boardIndex;
7146     char promoChar;
7147     char *p, *q;
7148     char buf[MSG_SIZ];
7149
7150     if (appData.debugMode)
7151       fprintf(debugFP, "Parsing game history: %s\n", game);
7152
7153     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7154     gameInfo.site = StrSave(appData.icsHost);
7155     gameInfo.date = PGNDate();
7156     gameInfo.round = StrSave("-");
7157
7158     /* Parse out names of players */
7159     while (*game == ' ') game++;
7160     p = buf;
7161     while (*game != ' ') *p++ = *game++;
7162     *p = NULLCHAR;
7163     gameInfo.white = StrSave(buf);
7164     while (*game == ' ') game++;
7165     p = buf;
7166     while (*game != ' ' && *game != '\n') *p++ = *game++;
7167     *p = NULLCHAR;
7168     gameInfo.black = StrSave(buf);
7169
7170     /* Parse moves */
7171     boardIndex = blackPlaysFirst ? 1 : 0;
7172     yynewstr(game);
7173     for (;;) {
7174         yyboardindex = boardIndex;
7175         moveType = (ChessMove) yylex();
7176         switch (moveType) {
7177           case IllegalMove:             /* maybe suicide chess, etc. */
7178   if (appData.debugMode) {
7179     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7180     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7181     setbuf(debugFP, NULL);
7182   }
7183           case WhitePromotionChancellor:
7184           case BlackPromotionChancellor:
7185           case WhitePromotionArchbishop:
7186           case BlackPromotionArchbishop:
7187           case WhitePromotionQueen:
7188           case BlackPromotionQueen:
7189           case WhitePromotionRook:
7190           case BlackPromotionRook:
7191           case WhitePromotionBishop:
7192           case BlackPromotionBishop:
7193           case WhitePromotionKnight:
7194           case BlackPromotionKnight:
7195           case WhitePromotionKing:
7196           case BlackPromotionKing:
7197           case NormalMove:
7198           case WhiteCapturesEnPassant:
7199           case BlackCapturesEnPassant:
7200           case WhiteKingSideCastle:
7201           case WhiteQueenSideCastle:
7202           case BlackKingSideCastle:
7203           case BlackQueenSideCastle:
7204           case WhiteKingSideCastleWild:
7205           case WhiteQueenSideCastleWild:
7206           case BlackKingSideCastleWild:
7207           case BlackQueenSideCastleWild:
7208           /* PUSH Fabien */
7209           case WhiteHSideCastleFR:
7210           case WhiteASideCastleFR:
7211           case BlackHSideCastleFR:
7212           case BlackASideCastleFR:
7213           /* POP Fabien */
7214             fromX = currentMoveString[0] - AAA;
7215             fromY = currentMoveString[1] - ONE;
7216             toX = currentMoveString[2] - AAA;
7217             toY = currentMoveString[3] - ONE;
7218             promoChar = currentMoveString[4];
7219             break;
7220           case WhiteDrop:
7221           case BlackDrop:
7222             fromX = moveType == WhiteDrop ?
7223               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7224             (int) CharToPiece(ToLower(currentMoveString[0]));
7225             fromY = DROP_RANK;
7226             toX = currentMoveString[2] - AAA;
7227             toY = currentMoveString[3] - ONE;
7228             promoChar = NULLCHAR;
7229             break;
7230           case AmbiguousMove:
7231             /* bug? */
7232             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7233   if (appData.debugMode) {
7234     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7235     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7236     setbuf(debugFP, NULL);
7237   }
7238             DisplayError(buf, 0);
7239             return;
7240           case ImpossibleMove:
7241             /* bug? */
7242             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7243   if (appData.debugMode) {
7244     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7246     setbuf(debugFP, NULL);
7247   }
7248             DisplayError(buf, 0);
7249             return;
7250           case (ChessMove) 0:   /* end of file */
7251             if (boardIndex < backwardMostMove) {
7252                 /* Oops, gap.  How did that happen? */
7253                 DisplayError(_("Gap in move list"), 0);
7254                 return;
7255             }
7256             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7257             if (boardIndex > forwardMostMove) {
7258                 forwardMostMove = boardIndex;
7259             }
7260             return;
7261           case ElapsedTime:
7262             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7263                 strcat(parseList[boardIndex-1], " ");
7264                 strcat(parseList[boardIndex-1], yy_text);
7265             }
7266             continue;
7267           case Comment:
7268           case PGNTag:
7269           case NAG:
7270           default:
7271             /* ignore */
7272             continue;
7273           case WhiteWins:
7274           case BlackWins:
7275           case GameIsDrawn:
7276           case GameUnfinished:
7277             if (gameMode == IcsExamining) {
7278                 if (boardIndex < backwardMostMove) {
7279                     /* Oops, gap.  How did that happen? */
7280                     return;
7281                 }
7282                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7283                 return;
7284             }
7285             gameInfo.result = moveType;
7286             p = strchr(yy_text, '{');
7287             if (p == NULL) p = strchr(yy_text, '(');
7288             if (p == NULL) {
7289                 p = yy_text;
7290                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7291             } else {
7292                 q = strchr(p, *p == '{' ? '}' : ')');
7293                 if (q != NULL) *q = NULLCHAR;
7294                 p++;
7295             }
7296             gameInfo.resultDetails = StrSave(p);
7297             continue;
7298         }
7299         if (boardIndex >= forwardMostMove &&
7300             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7301             backwardMostMove = blackPlaysFirst ? 1 : 0;
7302             return;
7303         }
7304         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7305                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7306                                  parseList[boardIndex]);
7307         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7308         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7309         /* currentMoveString is set as a side-effect of yylex */
7310         strcpy(moveList[boardIndex], currentMoveString);
7311         strcat(moveList[boardIndex], "\n");
7312         boardIndex++;
7313         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7314                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7315         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7316                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7317           case MT_NONE:
7318           case MT_STALEMATE:
7319           default:
7320             break;
7321           case MT_CHECK:
7322             if(gameInfo.variant != VariantShogi)
7323                 strcat(parseList[boardIndex - 1], "+");
7324             break;
7325           case MT_CHECKMATE:
7326           case MT_STAINMATE:
7327             strcat(parseList[boardIndex - 1], "#");
7328             break;
7329         }
7330     }
7331 }
7332
7333
7334 /* Apply a move to the given board  */
7335 void
7336 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7337      int fromX, fromY, toX, toY;
7338      int promoChar;
7339      Board board;
7340      char *castling;
7341      char *ep;
7342 {
7343   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7344
7345     /* [HGM] compute & store e.p. status and castling rights for new position */
7346     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7347     { int i;
7348
7349       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7350       oldEP = *ep;
7351       *ep = EP_NONE;
7352
7353       if( board[toY][toX] != EmptySquare ) 
7354            *ep = EP_CAPTURE;  
7355
7356       if( board[fromY][fromX] == WhitePawn ) {
7357            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7358                *ep = EP_PAWN_MOVE;
7359            if( toY-fromY==2) {
7360                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7361                         gameInfo.variant != VariantBerolina || toX < fromX)
7362                       *ep = toX | berolina;
7363                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7364                         gameInfo.variant != VariantBerolina || toX > fromX) 
7365                       *ep = toX;
7366            }
7367       } else 
7368       if( board[fromY][fromX] == BlackPawn ) {
7369            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7370                *ep = EP_PAWN_MOVE; 
7371            if( toY-fromY== -2) {
7372                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7373                         gameInfo.variant != VariantBerolina || toX < fromX)
7374                       *ep = toX | berolina;
7375                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7376                         gameInfo.variant != VariantBerolina || toX > fromX) 
7377                       *ep = toX;
7378            }
7379        }
7380
7381        for(i=0; i<nrCastlingRights; i++) {
7382            if(castling[i] == fromX && castlingRank[i] == fromY ||
7383               castling[i] == toX   && castlingRank[i] == toY   
7384              ) castling[i] = -1; // revoke for moved or captured piece
7385        }
7386
7387     }
7388
7389   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7390   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7391        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7392          
7393   if (fromX == toX && fromY == toY) return;
7394
7395   if (fromY == DROP_RANK) {
7396         /* must be first */
7397         piece = board[toY][toX] = (ChessSquare) fromX;
7398   } else {
7399      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7400      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7401      if(gameInfo.variant == VariantKnightmate)
7402          king += (int) WhiteUnicorn - (int) WhiteKing;
7403
7404     /* Code added by Tord: */
7405     /* FRC castling assumed when king captures friendly rook. */
7406     if (board[fromY][fromX] == WhiteKing &&
7407              board[toY][toX] == WhiteRook) {
7408       board[fromY][fromX] = EmptySquare;
7409       board[toY][toX] = EmptySquare;
7410       if(toX > fromX) {
7411         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7412       } else {
7413         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7414       }
7415     } else if (board[fromY][fromX] == BlackKing &&
7416                board[toY][toX] == BlackRook) {
7417       board[fromY][fromX] = EmptySquare;
7418       board[toY][toX] = EmptySquare;
7419       if(toX > fromX) {
7420         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7421       } else {
7422         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7423       }
7424     /* End of code added by Tord */
7425
7426     } else if (board[fromY][fromX] == king
7427         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7428         && toY == fromY && toX > fromX+1) {
7429         board[fromY][fromX] = EmptySquare;
7430         board[toY][toX] = king;
7431         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7432         board[fromY][BOARD_RGHT-1] = EmptySquare;
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_LEFT];
7439         board[fromY][BOARD_LEFT] = EmptySquare;
7440     } else if (board[fromY][fromX] == WhitePawn
7441                && toY == BOARD_HEIGHT-1
7442                && gameInfo.variant != VariantXiangqi
7443                ) {
7444         /* white pawn promotion */
7445         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7446         if (board[toY][toX] == EmptySquare) {
7447             board[toY][toX] = WhiteQueen;
7448         }
7449         if(gameInfo.variant==VariantBughouse ||
7450            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7451             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7452         board[fromY][fromX] = EmptySquare;
7453     } else if ((fromY == BOARD_HEIGHT-4)
7454                && (toX != fromX)
7455                && gameInfo.variant != VariantXiangqi
7456                && gameInfo.variant != VariantBerolina
7457                && (board[fromY][fromX] == WhitePawn)
7458                && (board[toY][toX] == EmptySquare)) {
7459         board[fromY][fromX] = EmptySquare;
7460         board[toY][toX] = WhitePawn;
7461         captured = board[toY - 1][toX];
7462         board[toY - 1][toX] = EmptySquare;
7463     } else if ((fromY == BOARD_HEIGHT-4)
7464                && (toX == fromX)
7465                && gameInfo.variant == VariantBerolina
7466                && (board[fromY][fromX] == WhitePawn)
7467                && (board[toY][toX] == EmptySquare)) {
7468         board[fromY][fromX] = EmptySquare;
7469         board[toY][toX] = WhitePawn;
7470         if(oldEP & EP_BEROLIN_A) {
7471                 captured = board[fromY][fromX-1];
7472                 board[fromY][fromX-1] = EmptySquare;
7473         }else{  captured = board[fromY][fromX+1];
7474                 board[fromY][fromX+1] = EmptySquare;
7475         }
7476     } else if (board[fromY][fromX] == king
7477         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7478                && toY == fromY && toX > fromX+1) {
7479         board[fromY][fromX] = EmptySquare;
7480         board[toY][toX] = king;
7481         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7482         board[fromY][BOARD_RGHT-1] = EmptySquare;
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_LEFT];
7489         board[fromY][BOARD_LEFT] = EmptySquare;
7490     } else if (fromY == 7 && fromX == 3
7491                && board[fromY][fromX] == BlackKing
7492                && toY == 7 && toX == 5) {
7493         board[fromY][fromX] = EmptySquare;
7494         board[toY][toX] = BlackKing;
7495         board[fromY][7] = EmptySquare;
7496         board[toY][4] = BlackRook;
7497     } else if (fromY == 7 && fromX == 3
7498                && board[fromY][fromX] == BlackKing
7499                && toY == 7 && toX == 1) {
7500         board[fromY][fromX] = EmptySquare;
7501         board[toY][toX] = BlackKing;
7502         board[fromY][0] = EmptySquare;
7503         board[toY][2] = BlackRook;
7504     } else if (board[fromY][fromX] == BlackPawn
7505                && toY == 0
7506                && gameInfo.variant != VariantXiangqi
7507                ) {
7508         /* black pawn promotion */
7509         board[0][toX] = CharToPiece(ToLower(promoChar));
7510         if (board[0][toX] == EmptySquare) {
7511             board[0][toX] = BlackQueen;
7512         }
7513         if(gameInfo.variant==VariantBughouse ||
7514            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7515             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7516         board[fromY][fromX] = EmptySquare;
7517     } else if ((fromY == 3)
7518                && (toX != fromX)
7519                && gameInfo.variant != VariantXiangqi
7520                && gameInfo.variant != VariantBerolina
7521                && (board[fromY][fromX] == BlackPawn)
7522                && (board[toY][toX] == EmptySquare)) {
7523         board[fromY][fromX] = EmptySquare;
7524         board[toY][toX] = BlackPawn;
7525         captured = board[toY + 1][toX];
7526         board[toY + 1][toX] = EmptySquare;
7527     } else if ((fromY == 3)
7528                && (toX == fromX)
7529                && gameInfo.variant == VariantBerolina
7530                && (board[fromY][fromX] == BlackPawn)
7531                && (board[toY][toX] == EmptySquare)) {
7532         board[fromY][fromX] = EmptySquare;
7533         board[toY][toX] = BlackPawn;
7534         if(oldEP & EP_BEROLIN_A) {
7535                 captured = board[fromY][fromX-1];
7536                 board[fromY][fromX-1] = EmptySquare;
7537         }else{  captured = board[fromY][fromX+1];
7538                 board[fromY][fromX+1] = EmptySquare;
7539         }
7540     } else {
7541         board[toY][toX] = board[fromY][fromX];
7542         board[fromY][fromX] = EmptySquare;
7543     }
7544
7545     /* [HGM] now we promote for Shogi, if needed */
7546     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7547         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7548   }
7549
7550     if (gameInfo.holdingsWidth != 0) {
7551
7552       /* !!A lot more code needs to be written to support holdings  */
7553       /* [HGM] OK, so I have written it. Holdings are stored in the */
7554       /* penultimate board files, so they are automaticlly stored   */
7555       /* in the game history.                                       */
7556       if (fromY == DROP_RANK) {
7557         /* Delete from holdings, by decreasing count */
7558         /* and erasing image if necessary            */
7559         p = (int) fromX;
7560         if(p < (int) BlackPawn) { /* white drop */
7561              p -= (int)WhitePawn;
7562                  p = PieceToNumber((ChessSquare)p);
7563              if(p >= gameInfo.holdingsSize) p = 0;
7564              if(--board[p][BOARD_WIDTH-2] <= 0)
7565                   board[p][BOARD_WIDTH-1] = EmptySquare;
7566              if((int)board[p][BOARD_WIDTH-2] < 0)
7567                         board[p][BOARD_WIDTH-2] = 0;
7568         } else {                  /* black drop */
7569              p -= (int)BlackPawn;
7570                  p = PieceToNumber((ChessSquare)p);
7571              if(p >= gameInfo.holdingsSize) p = 0;
7572              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7573                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7574              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7575                         board[BOARD_HEIGHT-1-p][1] = 0;
7576         }
7577       }
7578       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7579           && gameInfo.variant != VariantBughouse        ) {
7580         /* [HGM] holdings: Add to holdings, if holdings exist */
7581         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7582                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7583                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7584         }
7585         p = (int) captured;
7586         if (p >= (int) BlackPawn) {
7587           p -= (int)BlackPawn;
7588           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7589                   /* in Shogi restore piece to its original  first */
7590                   captured = (ChessSquare) (DEMOTED captured);
7591                   p = DEMOTED p;
7592           }
7593           p = PieceToNumber((ChessSquare)p);
7594           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7595           board[p][BOARD_WIDTH-2]++;
7596           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7597         } else {
7598           p -= (int)WhitePawn;
7599           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7600                   captured = (ChessSquare) (DEMOTED captured);
7601                   p = DEMOTED p;
7602           }
7603           p = PieceToNumber((ChessSquare)p);
7604           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7605           board[BOARD_HEIGHT-1-p][1]++;
7606           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7607         }
7608       }
7609     } else if (gameInfo.variant == VariantAtomic) {
7610       if (captured != EmptySquare) {
7611         int y, x;
7612         for (y = toY-1; y <= toY+1; y++) {
7613           for (x = toX-1; x <= toX+1; x++) {
7614             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7615                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7616               board[y][x] = EmptySquare;
7617             }
7618           }
7619         }
7620         board[toY][toX] = EmptySquare;
7621       }
7622     }
7623     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7624         /* [HGM] Shogi promotions */
7625         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7626     }
7627
7628     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7629                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7630         // [HGM] superchess: take promotion piece out of holdings
7631         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7632         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7633             if(!--board[k][BOARD_WIDTH-2])
7634                 board[k][BOARD_WIDTH-1] = EmptySquare;
7635         } else {
7636             if(!--board[BOARD_HEIGHT-1-k][1])
7637                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7638         }
7639     }
7640
7641 }
7642
7643 /* Updates forwardMostMove */
7644 void
7645 MakeMove(fromX, fromY, toX, toY, promoChar)
7646      int fromX, fromY, toX, toY;
7647      int promoChar;
7648 {
7649 //    forwardMostMove++; // [HGM] bare: moved downstream
7650
7651     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7652         int timeLeft; static int lastLoadFlag=0; int king, piece;
7653         piece = boards[forwardMostMove][fromY][fromX];
7654         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7655         if(gameInfo.variant == VariantKnightmate)
7656             king += (int) WhiteUnicorn - (int) WhiteKing;
7657         if(forwardMostMove == 0) {
7658             if(blackPlaysFirst) 
7659                 fprintf(serverMoves, "%s;", second.tidy);
7660             fprintf(serverMoves, "%s;", first.tidy);
7661             if(!blackPlaysFirst) 
7662                 fprintf(serverMoves, "%s;", second.tidy);
7663         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7664         lastLoadFlag = loadFlag;
7665         // print base move
7666         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7667         // print castling suffix
7668         if( toY == fromY && piece == king ) {
7669             if(toX-fromX > 1)
7670                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7671             if(fromX-toX >1)
7672                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7673         }
7674         // e.p. suffix
7675         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7676              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7677              boards[forwardMostMove][toY][toX] == EmptySquare
7678              && fromX != toX )
7679                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7680         // promotion suffix
7681         if(promoChar != NULLCHAR)
7682                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7683         if(!loadFlag) {
7684             fprintf(serverMoves, "/%d/%d",
7685                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7686             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7687             else                      timeLeft = blackTimeRemaining/1000;
7688             fprintf(serverMoves, "/%d", timeLeft);
7689         }
7690         fflush(serverMoves);
7691     }
7692
7693     if (forwardMostMove+1 >= MAX_MOVES) {
7694       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7695                         0, 1);
7696       return;
7697     }
7698     if (commentList[forwardMostMove+1] != NULL) {
7699         free(commentList[forwardMostMove+1]);
7700         commentList[forwardMostMove+1] = NULL;
7701     }
7702     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7703     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7704     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7705                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7706     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7707     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7708     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7709     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7710     gameInfo.result = GameUnfinished;
7711     if (gameInfo.resultDetails != NULL) {
7712         free(gameInfo.resultDetails);
7713         gameInfo.resultDetails = NULL;
7714     }
7715     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7716                               moveList[forwardMostMove - 1]);
7717     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7718                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7719                              fromY, fromX, toY, toX, promoChar,
7720                              parseList[forwardMostMove - 1]);
7721     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7722                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7723                             castlingRights[forwardMostMove]) ) {
7724       case MT_NONE:
7725       case MT_STALEMATE:
7726       default:
7727         break;
7728       case MT_CHECK:
7729         if(gameInfo.variant != VariantShogi)
7730             strcat(parseList[forwardMostMove - 1], "+");
7731         break;
7732       case MT_CHECKMATE:
7733       case MT_STAINMATE:
7734         strcat(parseList[forwardMostMove - 1], "#");
7735         break;
7736     }
7737     if (appData.debugMode) {
7738         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7739     }
7740
7741 }
7742
7743 /* Updates currentMove if not pausing */
7744 void
7745 ShowMove(fromX, fromY, toX, toY)
7746 {
7747     int instant = (gameMode == PlayFromGameFile) ?
7748         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7749     if(appData.noGUI) return;
7750     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7751         if (!instant) {
7752             if (forwardMostMove == currentMove + 1) {
7753                 AnimateMove(boards[forwardMostMove - 1],
7754                             fromX, fromY, toX, toY);
7755             }
7756             if (appData.highlightLastMove) {
7757                 SetHighlights(fromX, fromY, toX, toY);
7758             }
7759         }
7760         currentMove = forwardMostMove;
7761     }
7762
7763     if (instant) return;
7764
7765     DisplayMove(currentMove - 1);
7766     DrawPosition(FALSE, boards[currentMove]);
7767     DisplayBothClocks();
7768     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7769 }
7770
7771 void SendEgtPath(ChessProgramState *cps)
7772 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7773         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7774
7775         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7776
7777         while(*p) {
7778             char c, *q = name+1, *r, *s;
7779
7780             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7781             while(*p && *p != ',') *q++ = *p++;
7782             *q++ = ':'; *q = 0;
7783             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7784                 strcmp(name, ",nalimov:") == 0 ) {
7785                 // take nalimov path from the menu-changeable option first, if it is defined
7786                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7787                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7788             } else
7789             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7790                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7791                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7792                 s = r = StrStr(s, ":") + 1; // beginning of path info
7793                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7794                 c = *r; *r = 0;             // temporarily null-terminate path info
7795                     *--q = 0;               // strip of trailig ':' from name
7796                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7797                 *r = c;
7798                 SendToProgram(buf,cps);     // send egtbpath command for this format
7799             }
7800             if(*p == ',') p++; // read away comma to position for next format name
7801         }
7802 }
7803
7804 void
7805 InitChessProgram(cps, setup)
7806      ChessProgramState *cps;
7807      int setup; /* [HGM] needed to setup FRC opening position */
7808 {
7809     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7810     if (appData.noChessProgram) return;
7811     hintRequested = FALSE;
7812     bookRequested = FALSE;
7813
7814     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7815     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7816     if(cps->memSize) { /* [HGM] memory */
7817         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7818         SendToProgram(buf, cps);
7819     }
7820     SendEgtPath(cps); /* [HGM] EGT */
7821     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7822         sprintf(buf, "cores %d\n", appData.smpCores);
7823         SendToProgram(buf, cps);
7824     }
7825
7826     SendToProgram(cps->initString, cps);
7827     if (gameInfo.variant != VariantNormal &&
7828         gameInfo.variant != VariantLoadable
7829         /* [HGM] also send variant if board size non-standard */
7830         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7831                                             ) {
7832       char *v = VariantName(gameInfo.variant);
7833       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7834         /* [HGM] in protocol 1 we have to assume all variants valid */
7835         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7836         DisplayFatalError(buf, 0, 1);
7837         return;
7838       }
7839
7840       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7841       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7842       if( gameInfo.variant == VariantXiangqi )
7843            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7844       if( gameInfo.variant == VariantShogi )
7845            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7846       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7847            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7848       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7849                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7850            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7851       if( gameInfo.variant == VariantCourier )
7852            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7853       if( gameInfo.variant == VariantSuper )
7854            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7855       if( gameInfo.variant == VariantGreat )
7856            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7857
7858       if(overruled) {
7859            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7860                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7861            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7862            if(StrStr(cps->variants, b) == NULL) { 
7863                // specific sized variant not known, check if general sizing allowed
7864                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7865                    if(StrStr(cps->variants, "boardsize") == NULL) {
7866                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7867                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7868                        DisplayFatalError(buf, 0, 1);
7869                        return;
7870                    }
7871                    /* [HGM] here we really should compare with the maximum supported board size */
7872                }
7873            }
7874       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7875       sprintf(buf, "variant %s\n", b);
7876       SendToProgram(buf, cps);
7877     }
7878     currentlyInitializedVariant = gameInfo.variant;
7879
7880     /* [HGM] send opening position in FRC to first engine */
7881     if(setup) {
7882           SendToProgram("force\n", cps);
7883           SendBoard(cps, 0);
7884           /* engine is now in force mode! Set flag to wake it up after first move. */
7885           setboardSpoiledMachineBlack = 1;
7886     }
7887
7888     if (cps->sendICS) {
7889       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7890       SendToProgram(buf, cps);
7891     }
7892     cps->maybeThinking = FALSE;
7893     cps->offeredDraw = 0;
7894     if (!appData.icsActive) {
7895         SendTimeControl(cps, movesPerSession, timeControl,
7896                         timeIncrement, appData.searchDepth,
7897                         searchTime);
7898     }
7899     if (appData.showThinking 
7900         // [HGM] thinking: four options require thinking output to be sent
7901         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7902                                 ) {
7903         SendToProgram("post\n", cps);
7904     }
7905     SendToProgram("hard\n", cps);
7906     if (!appData.ponderNextMove) {
7907         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7908            it without being sure what state we are in first.  "hard"
7909            is not a toggle, so that one is OK.
7910          */
7911         SendToProgram("easy\n", cps);
7912     }
7913     if (cps->usePing) {
7914       sprintf(buf, "ping %d\n", ++cps->lastPing);
7915       SendToProgram(buf, cps);
7916     }
7917     cps->initDone = TRUE;
7918 }   
7919
7920
7921 void
7922 StartChessProgram(cps)
7923      ChessProgramState *cps;
7924 {
7925     char buf[MSG_SIZ];
7926     int err;
7927
7928     if (appData.noChessProgram) return;
7929     cps->initDone = FALSE;
7930
7931     if (strcmp(cps->host, "localhost") == 0) {
7932         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7933     } else if (*appData.remoteShell == NULLCHAR) {
7934         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7935     } else {
7936         if (*appData.remoteUser == NULLCHAR) {
7937           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7938                     cps->program);
7939         } else {
7940           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7941                     cps->host, appData.remoteUser, cps->program);
7942         }
7943         err = StartChildProcess(buf, "", &cps->pr);
7944     }
7945     
7946     if (err != 0) {
7947         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7948         DisplayFatalError(buf, err, 1);
7949         cps->pr = NoProc;
7950         cps->isr = NULL;
7951         return;
7952     }
7953     
7954     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7955     if (cps->protocolVersion > 1) {
7956       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7957       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7958       cps->comboCnt = 0;  //                and values of combo boxes
7959       SendToProgram(buf, cps);
7960     } else {
7961       SendToProgram("xboard\n", cps);
7962     }
7963 }
7964
7965
7966 void
7967 TwoMachinesEventIfReady P((void))
7968 {
7969   if (first.lastPing != first.lastPong) {
7970     DisplayMessage("", _("Waiting for first chess program"));
7971     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7972     return;
7973   }
7974   if (second.lastPing != second.lastPong) {
7975     DisplayMessage("", _("Waiting for second chess program"));
7976     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7977     return;
7978   }
7979   ThawUI();
7980   TwoMachinesEvent();
7981 }
7982
7983 void
7984 NextMatchGame P((void))
7985 {
7986     int index; /* [HGM] autoinc: step load index during match */
7987     Reset(FALSE, TRUE);
7988     if (*appData.loadGameFile != NULLCHAR) {
7989         index = appData.loadGameIndex;
7990         if(index < 0) { // [HGM] autoinc
7991             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7992             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7993         } 
7994         LoadGameFromFile(appData.loadGameFile,
7995                          index,
7996                          appData.loadGameFile, FALSE);
7997     } else if (*appData.loadPositionFile != NULLCHAR) {
7998         index = appData.loadPositionIndex;
7999         if(index < 0) { // [HGM] autoinc
8000             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8001             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8002         } 
8003         LoadPositionFromFile(appData.loadPositionFile,
8004                              index,
8005                              appData.loadPositionFile);
8006     }
8007     TwoMachinesEventIfReady();
8008 }
8009
8010 void UserAdjudicationEvent( int result )
8011 {
8012     ChessMove gameResult = GameIsDrawn;
8013
8014     if( result > 0 ) {
8015         gameResult = WhiteWins;
8016     }
8017     else if( result < 0 ) {
8018         gameResult = BlackWins;
8019     }
8020
8021     if( gameMode == TwoMachinesPlay ) {
8022         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8023     }
8024 }
8025
8026
8027 // [HGM] save: calculate checksum of game to make games easily identifiable
8028 int StringCheckSum(char *s)
8029 {
8030         int i = 0;
8031         if(s==NULL) return 0;
8032         while(*s) i = i*259 + *s++;
8033         return i;
8034 }
8035
8036 int GameCheckSum()
8037 {
8038         int i, sum=0;
8039         for(i=backwardMostMove; i<forwardMostMove; i++) {
8040                 sum += pvInfoList[i].depth;
8041                 sum += StringCheckSum(parseList[i]);
8042                 sum += StringCheckSum(commentList[i]);
8043                 sum *= 261;
8044         }
8045         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8046         return sum + StringCheckSum(commentList[i]);
8047 } // end of save patch
8048
8049 void
8050 GameEnds(result, resultDetails, whosays)
8051      ChessMove result;
8052      char *resultDetails;
8053      int whosays;
8054 {
8055     GameMode nextGameMode;
8056     int isIcsGame;
8057     char buf[MSG_SIZ];
8058
8059     if(endingGame) return; /* [HGM] crash: forbid recursion */
8060     endingGame = 1;
8061
8062     if (appData.debugMode) {
8063       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8064               result, resultDetails ? resultDetails : "(null)", whosays);
8065     }
8066
8067     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8068         /* If we are playing on ICS, the server decides when the
8069            game is over, but the engine can offer to draw, claim 
8070            a draw, or resign. 
8071          */
8072 #if ZIPPY
8073         if (appData.zippyPlay && first.initDone) {
8074             if (result == GameIsDrawn) {
8075                 /* In case draw still needs to be claimed */
8076                 SendToICS(ics_prefix);
8077                 SendToICS("draw\n");
8078             } else if (StrCaseStr(resultDetails, "resign")) {
8079                 SendToICS(ics_prefix);
8080                 SendToICS("resign\n");
8081             }
8082         }
8083 #endif
8084         endingGame = 0; /* [HGM] crash */
8085         return;
8086     }
8087
8088     /* If we're loading the game from a file, stop */
8089     if (whosays == GE_FILE) {
8090       (void) StopLoadGameTimer();
8091       gameFileFP = NULL;
8092     }
8093
8094     /* Cancel draw offers */
8095     first.offeredDraw = second.offeredDraw = 0;
8096
8097     /* If this is an ICS game, only ICS can really say it's done;
8098        if not, anyone can. */
8099     isIcsGame = (gameMode == IcsPlayingWhite || 
8100                  gameMode == IcsPlayingBlack || 
8101                  gameMode == IcsObserving    || 
8102                  gameMode == IcsExamining);
8103
8104     if (!isIcsGame || whosays == GE_ICS) {
8105         /* OK -- not an ICS game, or ICS said it was done */
8106         StopClocks();
8107         if (!isIcsGame && !appData.noChessProgram) 
8108           SetUserThinkingEnables();
8109     
8110         /* [HGM] if a machine claims the game end we verify this claim */
8111         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8112             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8113                 char claimer;
8114                 ChessMove trueResult = (ChessMove) -1;
8115
8116                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8117                                             first.twoMachinesColor[0] :
8118                                             second.twoMachinesColor[0] ;
8119
8120                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8121                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8122                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8123                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8124                 } else
8125                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8126                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8127                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8128                 } else
8129                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8130                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8131                 }
8132
8133                 // now verify win claims, but not in drop games, as we don't understand those yet
8134                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8135                                                  || gameInfo.variant == VariantGreat) &&
8136                     (result == WhiteWins && claimer == 'w' ||
8137                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8138                       if (appData.debugMode) {
8139                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8140                                 result, epStatus[forwardMostMove], forwardMostMove);
8141                       }
8142                       if(result != trueResult) {
8143                               sprintf(buf, "False win claim: '%s'", resultDetails);
8144                               result = claimer == 'w' ? BlackWins : WhiteWins;
8145                               resultDetails = buf;
8146                       }
8147                 } else
8148                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8149                     && (forwardMostMove <= backwardMostMove ||
8150                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8151                         (claimer=='b')==(forwardMostMove&1))
8152                                                                                   ) {
8153                       /* [HGM] verify: draws that were not flagged are false claims */
8154                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8155                       result = claimer == 'w' ? BlackWins : WhiteWins;
8156                       resultDetails = buf;
8157                 }
8158                 /* (Claiming a loss is accepted no questions asked!) */
8159             }
8160             /* [HGM] bare: don't allow bare King to win */
8161             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8162                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8163                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8164                && result != GameIsDrawn)
8165             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8166                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8167                         int p = (int)boards[forwardMostMove][i][j] - color;
8168                         if(p >= 0 && p <= (int)WhiteKing) k++;
8169                 }
8170                 if (appData.debugMode) {
8171                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8172                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8173                 }
8174                 if(k <= 1) {
8175                         result = GameIsDrawn;
8176                         sprintf(buf, "%s but bare king", resultDetails);
8177                         resultDetails = buf;
8178                 }
8179             }
8180         }
8181
8182
8183         if(serverMoves != NULL && !loadFlag) { char c = '=';
8184             if(result==WhiteWins) c = '+';
8185             if(result==BlackWins) c = '-';
8186             if(resultDetails != NULL)
8187                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8188         }
8189         if (resultDetails != NULL) {
8190             gameInfo.result = result;
8191             gameInfo.resultDetails = StrSave(resultDetails);
8192
8193             /* display last move only if game was not loaded from file */
8194             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8195                 DisplayMove(currentMove - 1);
8196     
8197             if (forwardMostMove != 0) {
8198                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8199                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8200                                                                 ) {
8201                     if (*appData.saveGameFile != NULLCHAR) {
8202                         SaveGameToFile(appData.saveGameFile, TRUE);
8203                     } else if (appData.autoSaveGames) {
8204                         AutoSaveGame();
8205                     }
8206                     if (*appData.savePositionFile != NULLCHAR) {
8207                         SavePositionToFile(appData.savePositionFile);
8208                     }
8209                 }
8210             }
8211
8212             /* Tell program how game ended in case it is learning */
8213             /* [HGM] Moved this to after saving the PGN, just in case */
8214             /* engine died and we got here through time loss. In that */
8215             /* case we will get a fatal error writing the pipe, which */
8216             /* would otherwise lose us the PGN.                       */
8217             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8218             /* output during GameEnds should never be fatal anymore   */
8219             if (gameMode == MachinePlaysWhite ||
8220                 gameMode == MachinePlaysBlack ||
8221                 gameMode == TwoMachinesPlay ||
8222                 gameMode == IcsPlayingWhite ||
8223                 gameMode == IcsPlayingBlack ||
8224                 gameMode == BeginningOfGame) {
8225                 char buf[MSG_SIZ];
8226                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8227                         resultDetails);
8228                 if (first.pr != NoProc) {
8229                     SendToProgram(buf, &first);
8230                 }
8231                 if (second.pr != NoProc &&
8232                     gameMode == TwoMachinesPlay) {
8233                     SendToProgram(buf, &second);
8234                 }
8235             }
8236         }
8237
8238         if (appData.icsActive) {
8239             if (appData.quietPlay &&
8240                 (gameMode == IcsPlayingWhite ||
8241                  gameMode == IcsPlayingBlack)) {
8242                 SendToICS(ics_prefix);
8243                 SendToICS("set shout 1\n");
8244             }
8245             nextGameMode = IcsIdle;
8246             ics_user_moved = FALSE;
8247             /* clean up premove.  It's ugly when the game has ended and the
8248              * premove highlights are still on the board.
8249              */
8250             if (gotPremove) {
8251               gotPremove = FALSE;
8252               ClearPremoveHighlights();
8253               DrawPosition(FALSE, boards[currentMove]);
8254             }
8255             if (whosays == GE_ICS) {
8256                 switch (result) {
8257                 case WhiteWins:
8258                     if (gameMode == IcsPlayingWhite)
8259                         PlayIcsWinSound();
8260                     else if(gameMode == IcsPlayingBlack)
8261                         PlayIcsLossSound();
8262                     break;
8263                 case BlackWins:
8264                     if (gameMode == IcsPlayingBlack)
8265                         PlayIcsWinSound();
8266                     else if(gameMode == IcsPlayingWhite)
8267                         PlayIcsLossSound();
8268                     break;
8269                 case GameIsDrawn:
8270                     PlayIcsDrawSound();
8271                     break;
8272                 default:
8273                     PlayIcsUnfinishedSound();
8274                 }
8275             }
8276         } else if (gameMode == EditGame ||
8277                    gameMode == PlayFromGameFile || 
8278                    gameMode == AnalyzeMode || 
8279                    gameMode == AnalyzeFile) {
8280             nextGameMode = gameMode;
8281         } else {
8282             nextGameMode = EndOfGame;
8283         }
8284         pausing = FALSE;
8285         ModeHighlight();
8286     } else {
8287         nextGameMode = gameMode;
8288     }
8289
8290     if (appData.noChessProgram) {
8291         gameMode = nextGameMode;
8292         ModeHighlight();
8293         endingGame = 0; /* [HGM] crash */
8294         return;
8295     }
8296
8297     if (first.reuse) {
8298         /* Put first chess program into idle state */
8299         if (first.pr != NoProc &&
8300             (gameMode == MachinePlaysWhite ||
8301              gameMode == MachinePlaysBlack ||
8302              gameMode == TwoMachinesPlay ||
8303              gameMode == IcsPlayingWhite ||
8304              gameMode == IcsPlayingBlack ||
8305              gameMode == BeginningOfGame)) {
8306             SendToProgram("force\n", &first);
8307             if (first.usePing) {
8308               char buf[MSG_SIZ];
8309               sprintf(buf, "ping %d\n", ++first.lastPing);
8310               SendToProgram(buf, &first);
8311             }
8312         }
8313     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8314         /* Kill off first chess program */
8315         if (first.isr != NULL)
8316           RemoveInputSource(first.isr);
8317         first.isr = NULL;
8318     
8319         if (first.pr != NoProc) {
8320             ExitAnalyzeMode();
8321             DoSleep( appData.delayBeforeQuit );
8322             SendToProgram("quit\n", &first);
8323             DoSleep( appData.delayAfterQuit );
8324             DestroyChildProcess(first.pr, first.useSigterm);
8325         }
8326         first.pr = NoProc;
8327     }
8328     if (second.reuse) {
8329         /* Put second chess program into idle state */
8330         if (second.pr != NoProc &&
8331             gameMode == TwoMachinesPlay) {
8332             SendToProgram("force\n", &second);
8333             if (second.usePing) {
8334               char buf[MSG_SIZ];
8335               sprintf(buf, "ping %d\n", ++second.lastPing);
8336               SendToProgram(buf, &second);
8337             }
8338         }
8339     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8340         /* Kill off second chess program */
8341         if (second.isr != NULL)
8342           RemoveInputSource(second.isr);
8343         second.isr = NULL;
8344     
8345         if (second.pr != NoProc) {
8346             DoSleep( appData.delayBeforeQuit );
8347             SendToProgram("quit\n", &second);
8348             DoSleep( appData.delayAfterQuit );
8349             DestroyChildProcess(second.pr, second.useSigterm);
8350         }
8351         second.pr = NoProc;
8352     }
8353
8354     if (matchMode && gameMode == TwoMachinesPlay) {
8355         switch (result) {
8356         case WhiteWins:
8357           if (first.twoMachinesColor[0] == 'w') {
8358             first.matchWins++;
8359           } else {
8360             second.matchWins++;
8361           }
8362           break;
8363         case BlackWins:
8364           if (first.twoMachinesColor[0] == 'b') {
8365             first.matchWins++;
8366           } else {
8367             second.matchWins++;
8368           }
8369           break;
8370         default:
8371           break;
8372         }
8373         if (matchGame < appData.matchGames) {
8374             char *tmp;
8375             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8376                 tmp = first.twoMachinesColor;
8377                 first.twoMachinesColor = second.twoMachinesColor;
8378                 second.twoMachinesColor = tmp;
8379             }
8380             gameMode = nextGameMode;
8381             matchGame++;
8382             if(appData.matchPause>10000 || appData.matchPause<10)
8383                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8384             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8385             endingGame = 0; /* [HGM] crash */
8386             return;
8387         } else {
8388             char buf[MSG_SIZ];
8389             gameMode = nextGameMode;
8390             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8391                     first.tidy, second.tidy,
8392                     first.matchWins, second.matchWins,
8393                     appData.matchGames - (first.matchWins + second.matchWins));
8394             DisplayFatalError(buf, 0, 0);
8395         }
8396     }
8397     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8398         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8399       ExitAnalyzeMode();
8400     gameMode = nextGameMode;
8401     ModeHighlight();
8402     endingGame = 0;  /* [HGM] crash */
8403 }
8404
8405 /* Assumes program was just initialized (initString sent).
8406    Leaves program in force mode. */
8407 void
8408 FeedMovesToProgram(cps, upto) 
8409      ChessProgramState *cps;
8410      int upto;
8411 {
8412     int i;
8413     
8414     if (appData.debugMode)
8415       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8416               startedFromSetupPosition ? "position and " : "",
8417               backwardMostMove, upto, cps->which);
8418     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8419         // [HGM] variantswitch: make engine aware of new variant
8420         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8421                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8422         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8423         SendToProgram(buf, cps);
8424         currentlyInitializedVariant = gameInfo.variant;
8425     }
8426     SendToProgram("force\n", cps);
8427     if (startedFromSetupPosition) {
8428         SendBoard(cps, backwardMostMove);
8429     if (appData.debugMode) {
8430         fprintf(debugFP, "feedMoves\n");
8431     }
8432     }
8433     for (i = backwardMostMove; i < upto; i++) {
8434         SendMoveToProgram(i, cps);
8435     }
8436 }
8437
8438
8439 void
8440 ResurrectChessProgram()
8441 {
8442      /* The chess program may have exited.
8443         If so, restart it and feed it all the moves made so far. */
8444
8445     if (appData.noChessProgram || first.pr != NoProc) return;
8446     
8447     StartChessProgram(&first);
8448     InitChessProgram(&first, FALSE);
8449     FeedMovesToProgram(&first, currentMove);
8450
8451     if (!first.sendTime) {
8452         /* can't tell gnuchess what its clock should read,
8453            so we bow to its notion. */
8454         ResetClocks();
8455         timeRemaining[0][currentMove] = whiteTimeRemaining;
8456         timeRemaining[1][currentMove] = blackTimeRemaining;
8457     }
8458
8459     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8460                 appData.icsEngineAnalyze) && first.analysisSupport) {
8461       SendToProgram("analyze\n", &first);
8462       first.analyzing = TRUE;
8463     }
8464 }
8465
8466 /*
8467  * Button procedures
8468  */
8469 void
8470 Reset(redraw, init)
8471      int redraw, init;
8472 {
8473     int i;
8474
8475     if (appData.debugMode) {
8476         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8477                 redraw, init, gameMode);
8478     }
8479     pausing = pauseExamInvalid = FALSE;
8480     startedFromSetupPosition = blackPlaysFirst = FALSE;
8481     firstMove = TRUE;
8482     whiteFlag = blackFlag = FALSE;
8483     userOfferedDraw = FALSE;
8484     hintRequested = bookRequested = FALSE;
8485     first.maybeThinking = FALSE;
8486     second.maybeThinking = FALSE;
8487     first.bookSuspend = FALSE; // [HGM] book
8488     second.bookSuspend = FALSE;
8489     thinkOutput[0] = NULLCHAR;
8490     lastHint[0] = NULLCHAR;
8491     ClearGameInfo(&gameInfo);
8492     gameInfo.variant = StringToVariant(appData.variant);
8493     ics_user_moved = ics_clock_paused = FALSE;
8494     ics_getting_history = H_FALSE;
8495     ics_gamenum = -1;
8496     white_holding[0] = black_holding[0] = NULLCHAR;
8497     ClearProgramStats();
8498     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8499     
8500     ResetFrontEnd();
8501     ClearHighlights();
8502     flipView = appData.flipView;
8503     ClearPremoveHighlights();
8504     gotPremove = FALSE;
8505     alarmSounded = FALSE;
8506
8507     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8508     if(appData.serverMovesName != NULL) {
8509         /* [HGM] prepare to make moves file for broadcasting */
8510         clock_t t = clock();
8511         if(serverMoves != NULL) fclose(serverMoves);
8512         serverMoves = fopen(appData.serverMovesName, "r");
8513         if(serverMoves != NULL) {
8514             fclose(serverMoves);
8515             /* delay 15 sec before overwriting, so all clients can see end */
8516             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8517         }
8518         serverMoves = fopen(appData.serverMovesName, "w");
8519     }
8520
8521     ExitAnalyzeMode();
8522     gameMode = BeginningOfGame;
8523     ModeHighlight();
8524     if(appData.icsActive) gameInfo.variant = VariantNormal;
8525     currentMove = forwardMostMove = backwardMostMove = 0;
8526     InitPosition(redraw);
8527     for (i = 0; i < MAX_MOVES; i++) {
8528         if (commentList[i] != NULL) {
8529             free(commentList[i]);
8530             commentList[i] = NULL;
8531         }
8532     }
8533     ResetClocks();
8534     timeRemaining[0][0] = whiteTimeRemaining;
8535     timeRemaining[1][0] = blackTimeRemaining;
8536     if (first.pr == NULL) {
8537         StartChessProgram(&first);
8538     }
8539     if (init) {
8540             InitChessProgram(&first, startedFromSetupPosition);
8541     }
8542     DisplayTitle("");
8543     DisplayMessage("", "");
8544     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8545     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8546 }
8547
8548 void
8549 AutoPlayGameLoop()
8550 {
8551     for (;;) {
8552         if (!AutoPlayOneMove())
8553           return;
8554         if (matchMode || appData.timeDelay == 0)
8555           continue;
8556         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8557           return;
8558         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8559         break;
8560     }
8561 }
8562
8563
8564 int
8565 AutoPlayOneMove()
8566 {
8567     int fromX, fromY, toX, toY;
8568
8569     if (appData.debugMode) {
8570       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8571     }
8572
8573     if (gameMode != PlayFromGameFile)
8574       return FALSE;
8575
8576     if (currentMove >= forwardMostMove) {
8577       gameMode = EditGame;
8578       ModeHighlight();
8579
8580       /* [AS] Clear current move marker at the end of a game */
8581       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8582
8583       return FALSE;
8584     }
8585     
8586     toX = moveList[currentMove][2] - AAA;
8587     toY = moveList[currentMove][3] - ONE;
8588
8589     if (moveList[currentMove][1] == '@') {
8590         if (appData.highlightLastMove) {
8591             SetHighlights(-1, -1, toX, toY);
8592         }
8593     } else {
8594         fromX = moveList[currentMove][0] - AAA;
8595         fromY = moveList[currentMove][1] - ONE;
8596
8597         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8598
8599         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8600
8601         if (appData.highlightLastMove) {
8602             SetHighlights(fromX, fromY, toX, toY);
8603         }
8604     }
8605     DisplayMove(currentMove);
8606     SendMoveToProgram(currentMove++, &first);
8607     DisplayBothClocks();
8608     DrawPosition(FALSE, boards[currentMove]);
8609     // [HGM] PV info: always display, routine tests if empty
8610     DisplayComment(currentMove - 1, commentList[currentMove]);
8611     return TRUE;
8612 }
8613
8614
8615 int
8616 LoadGameOneMove(readAhead)
8617      ChessMove readAhead;
8618 {
8619     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8620     char promoChar = NULLCHAR;
8621     ChessMove moveType;
8622     char move[MSG_SIZ];
8623     char *p, *q;
8624     
8625     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8626         gameMode != AnalyzeMode && gameMode != Training) {
8627         gameFileFP = NULL;
8628         return FALSE;
8629     }
8630     
8631     yyboardindex = forwardMostMove;
8632     if (readAhead != (ChessMove)0) {
8633       moveType = readAhead;
8634     } else {
8635       if (gameFileFP == NULL)
8636           return FALSE;
8637       moveType = (ChessMove) yylex();
8638     }
8639     
8640     done = FALSE;
8641     switch (moveType) {
8642       case Comment:
8643         if (appData.debugMode) 
8644           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8645         p = yy_text;
8646         if (*p == '{' || *p == '[' || *p == '(') {
8647             p[strlen(p) - 1] = NULLCHAR;
8648             p++;
8649         }
8650
8651         /* append the comment but don't display it */
8652         while (*p == '\n') p++;
8653         AppendComment(currentMove, p);
8654         return TRUE;
8655
8656       case WhiteCapturesEnPassant:
8657       case BlackCapturesEnPassant:
8658       case WhitePromotionChancellor:
8659       case BlackPromotionChancellor:
8660       case WhitePromotionArchbishop:
8661       case BlackPromotionArchbishop:
8662       case WhitePromotionCentaur:
8663       case BlackPromotionCentaur:
8664       case WhitePromotionQueen:
8665       case BlackPromotionQueen:
8666       case WhitePromotionRook:
8667       case BlackPromotionRook:
8668       case WhitePromotionBishop:
8669       case BlackPromotionBishop:
8670       case WhitePromotionKnight:
8671       case BlackPromotionKnight:
8672       case WhitePromotionKing:
8673       case BlackPromotionKing:
8674       case NormalMove:
8675       case WhiteKingSideCastle:
8676       case WhiteQueenSideCastle:
8677       case BlackKingSideCastle:
8678       case BlackQueenSideCastle:
8679       case WhiteKingSideCastleWild:
8680       case WhiteQueenSideCastleWild:
8681       case BlackKingSideCastleWild:
8682       case BlackQueenSideCastleWild:
8683       /* PUSH Fabien */
8684       case WhiteHSideCastleFR:
8685       case WhiteASideCastleFR:
8686       case BlackHSideCastleFR:
8687       case BlackASideCastleFR:
8688       /* POP Fabien */
8689         if (appData.debugMode)
8690           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8691         fromX = currentMoveString[0] - AAA;
8692         fromY = currentMoveString[1] - ONE;
8693         toX = currentMoveString[2] - AAA;
8694         toY = currentMoveString[3] - ONE;
8695         promoChar = currentMoveString[4];
8696         break;
8697
8698       case WhiteDrop:
8699       case BlackDrop:
8700         if (appData.debugMode)
8701           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8702         fromX = moveType == WhiteDrop ?
8703           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8704         (int) CharToPiece(ToLower(currentMoveString[0]));
8705         fromY = DROP_RANK;
8706         toX = currentMoveString[2] - AAA;
8707         toY = currentMoveString[3] - ONE;
8708         break;
8709
8710       case WhiteWins:
8711       case BlackWins:
8712       case GameIsDrawn:
8713       case GameUnfinished:
8714         if (appData.debugMode)
8715           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8716         p = strchr(yy_text, '{');
8717         if (p == NULL) p = strchr(yy_text, '(');
8718         if (p == NULL) {
8719             p = yy_text;
8720             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8721         } else {
8722             q = strchr(p, *p == '{' ? '}' : ')');
8723             if (q != NULL) *q = NULLCHAR;
8724             p++;
8725         }
8726         GameEnds(moveType, p, GE_FILE);
8727         done = TRUE;
8728         if (cmailMsgLoaded) {
8729             ClearHighlights();
8730             flipView = WhiteOnMove(currentMove);
8731             if (moveType == GameUnfinished) flipView = !flipView;
8732             if (appData.debugMode)
8733               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8734         }
8735         break;
8736
8737       case (ChessMove) 0:       /* end of file */
8738         if (appData.debugMode)
8739           fprintf(debugFP, "Parser hit end of file\n");
8740         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8741                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8742           case MT_NONE:
8743           case MT_CHECK:
8744             break;
8745           case MT_CHECKMATE:
8746           case MT_STAINMATE:
8747             if (WhiteOnMove(currentMove)) {
8748                 GameEnds(BlackWins, "Black mates", GE_FILE);
8749             } else {
8750                 GameEnds(WhiteWins, "White mates", GE_FILE);
8751             }
8752             break;
8753           case MT_STALEMATE:
8754             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8755             break;
8756         }
8757         done = TRUE;
8758         break;
8759
8760       case MoveNumberOne:
8761         if (lastLoadGameStart == GNUChessGame) {
8762             /* GNUChessGames have numbers, but they aren't move numbers */
8763             if (appData.debugMode)
8764               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8765                       yy_text, (int) moveType);
8766             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8767         }
8768         /* else fall thru */
8769
8770       case XBoardGame:
8771       case GNUChessGame:
8772       case PGNTag:
8773         /* Reached start of next game in file */
8774         if (appData.debugMode)
8775           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8776         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8777                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8778           case MT_NONE:
8779           case MT_CHECK:
8780             break;
8781           case MT_CHECKMATE:
8782           case MT_STAINMATE:
8783             if (WhiteOnMove(currentMove)) {
8784                 GameEnds(BlackWins, "Black mates", GE_FILE);
8785             } else {
8786                 GameEnds(WhiteWins, "White mates", GE_FILE);
8787             }
8788             break;
8789           case MT_STALEMATE:
8790             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8791             break;
8792         }
8793         done = TRUE;
8794         break;
8795
8796       case PositionDiagram:     /* should not happen; ignore */
8797       case ElapsedTime:         /* ignore */
8798       case NAG:                 /* ignore */
8799         if (appData.debugMode)
8800           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8801                   yy_text, (int) moveType);
8802         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8803
8804       case IllegalMove:
8805         if (appData.testLegality) {
8806             if (appData.debugMode)
8807               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8808             sprintf(move, _("Illegal move: %d.%s%s"),
8809                     (forwardMostMove / 2) + 1,
8810                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8811             DisplayError(move, 0);
8812             done = TRUE;
8813         } else {
8814             if (appData.debugMode)
8815               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8816                       yy_text, currentMoveString);
8817             fromX = currentMoveString[0] - AAA;
8818             fromY = currentMoveString[1] - ONE;
8819             toX = currentMoveString[2] - AAA;
8820             toY = currentMoveString[3] - ONE;
8821             promoChar = currentMoveString[4];
8822         }
8823         break;
8824
8825       case AmbiguousMove:
8826         if (appData.debugMode)
8827           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8828         sprintf(move, _("Ambiguous move: %d.%s%s"),
8829                 (forwardMostMove / 2) + 1,
8830                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8831         DisplayError(move, 0);
8832         done = TRUE;
8833         break;
8834
8835       default:
8836       case ImpossibleMove:
8837         if (appData.debugMode)
8838           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8839         sprintf(move, _("Illegal move: %d.%s%s"),
8840                 (forwardMostMove / 2) + 1,
8841                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8842         DisplayError(move, 0);
8843         done = TRUE;
8844         break;
8845     }
8846
8847     if (done) {
8848         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8849             DrawPosition(FALSE, boards[currentMove]);
8850             DisplayBothClocks();
8851             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8852               DisplayComment(currentMove - 1, commentList[currentMove]);
8853         }
8854         (void) StopLoadGameTimer();
8855         gameFileFP = NULL;
8856         cmailOldMove = forwardMostMove;
8857         return FALSE;
8858     } else {
8859         /* currentMoveString is set as a side-effect of yylex */
8860         strcat(currentMoveString, "\n");
8861         strcpy(moveList[forwardMostMove], currentMoveString);
8862         
8863         thinkOutput[0] = NULLCHAR;
8864         MakeMove(fromX, fromY, toX, toY, promoChar);
8865         currentMove = forwardMostMove;
8866         return TRUE;
8867     }
8868 }
8869
8870 /* Load the nth game from the given file */
8871 int
8872 LoadGameFromFile(filename, n, title, useList)
8873      char *filename;
8874      int n;
8875      char *title;
8876      /*Boolean*/ int useList;
8877 {
8878     FILE *f;
8879     char buf[MSG_SIZ];
8880
8881     if (strcmp(filename, "-") == 0) {
8882         f = stdin;
8883         title = "stdin";
8884     } else {
8885         f = fopen(filename, "rb");
8886         if (f == NULL) {
8887           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8888             DisplayError(buf, errno);
8889             return FALSE;
8890         }
8891     }
8892     if (fseek(f, 0, 0) == -1) {
8893         /* f is not seekable; probably a pipe */
8894         useList = FALSE;
8895     }
8896     if (useList && n == 0) {
8897         int error = GameListBuild(f);
8898         if (error) {
8899             DisplayError(_("Cannot build game list"), error);
8900         } else if (!ListEmpty(&gameList) &&
8901                    ((ListGame *) gameList.tailPred)->number > 1) {
8902             GameListPopUp(f, title);
8903             return TRUE;
8904         }
8905         GameListDestroy();
8906         n = 1;
8907     }
8908     if (n == 0) n = 1;
8909     return LoadGame(f, n, title, FALSE);
8910 }
8911
8912
8913 void
8914 MakeRegisteredMove()
8915 {
8916     int fromX, fromY, toX, toY;
8917     char promoChar;
8918     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8919         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8920           case CMAIL_MOVE:
8921           case CMAIL_DRAW:
8922             if (appData.debugMode)
8923               fprintf(debugFP, "Restoring %s for game %d\n",
8924                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8925     
8926             thinkOutput[0] = NULLCHAR;
8927             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8928             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8929             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8930             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8931             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8932             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8933             MakeMove(fromX, fromY, toX, toY, promoChar);
8934             ShowMove(fromX, fromY, toX, toY);
8935               
8936             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8937                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8938               case MT_NONE:
8939               case MT_CHECK:
8940                 break;
8941                 
8942               case MT_CHECKMATE:
8943               case MT_STAINMATE:
8944                 if (WhiteOnMove(currentMove)) {
8945                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8946                 } else {
8947                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8948                 }
8949                 break;
8950                 
8951               case MT_STALEMATE:
8952                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8953                 break;
8954             }
8955
8956             break;
8957             
8958           case CMAIL_RESIGN:
8959             if (WhiteOnMove(currentMove)) {
8960                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8961             } else {
8962                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8963             }
8964             break;
8965             
8966           case CMAIL_ACCEPT:
8967             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8968             break;
8969               
8970           default:
8971             break;
8972         }
8973     }
8974
8975     return;
8976 }
8977
8978 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8979 int
8980 CmailLoadGame(f, gameNumber, title, useList)
8981      FILE *f;
8982      int gameNumber;
8983      char *title;
8984      int useList;
8985 {
8986     int retVal;
8987
8988     if (gameNumber > nCmailGames) {
8989         DisplayError(_("No more games in this message"), 0);
8990         return FALSE;
8991     }
8992     if (f == lastLoadGameFP) {
8993         int offset = gameNumber - lastLoadGameNumber;
8994         if (offset == 0) {
8995             cmailMsg[0] = NULLCHAR;
8996             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8997                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8998                 nCmailMovesRegistered--;
8999             }
9000             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9001             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9002                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9003             }
9004         } else {
9005             if (! RegisterMove()) return FALSE;
9006         }
9007     }
9008
9009     retVal = LoadGame(f, gameNumber, title, useList);
9010
9011     /* Make move registered during previous look at this game, if any */
9012     MakeRegisteredMove();
9013
9014     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9015         commentList[currentMove]
9016           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9017         DisplayComment(currentMove - 1, commentList[currentMove]);
9018     }
9019
9020     return retVal;
9021 }
9022
9023 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9024 int
9025 ReloadGame(offset)
9026      int offset;
9027 {
9028     int gameNumber = lastLoadGameNumber + offset;
9029     if (lastLoadGameFP == NULL) {
9030         DisplayError(_("No game has been loaded yet"), 0);
9031         return FALSE;
9032     }
9033     if (gameNumber <= 0) {
9034         DisplayError(_("Can't back up any further"), 0);
9035         return FALSE;
9036     }
9037     if (cmailMsgLoaded) {
9038         return CmailLoadGame(lastLoadGameFP, gameNumber,
9039                              lastLoadGameTitle, lastLoadGameUseList);
9040     } else {
9041         return LoadGame(lastLoadGameFP, gameNumber,
9042                         lastLoadGameTitle, lastLoadGameUseList);
9043     }
9044 }
9045
9046
9047
9048 /* Load the nth game from open file f */
9049 int
9050 LoadGame(f, gameNumber, title, useList)
9051      FILE *f;
9052      int gameNumber;
9053      char *title;
9054      int useList;
9055 {
9056     ChessMove cm;
9057     char buf[MSG_SIZ];
9058     int gn = gameNumber;
9059     ListGame *lg = NULL;
9060     int numPGNTags = 0;
9061     int err;
9062     GameMode oldGameMode;
9063     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9064
9065     if (appData.debugMode) 
9066         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9067
9068     if (gameMode == Training )
9069         SetTrainingModeOff();
9070
9071     oldGameMode = gameMode;
9072     if (gameMode != BeginningOfGame) {
9073       Reset(FALSE, TRUE);
9074     }
9075
9076     gameFileFP = f;
9077     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9078         fclose(lastLoadGameFP);
9079     }
9080
9081     if (useList) {
9082         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9083         
9084         if (lg) {
9085             fseek(f, lg->offset, 0);
9086             GameListHighlight(gameNumber);
9087             gn = 1;
9088         }
9089         else {
9090             DisplayError(_("Game number out of range"), 0);
9091             return FALSE;
9092         }
9093     } else {
9094         GameListDestroy();
9095         if (fseek(f, 0, 0) == -1) {
9096             if (f == lastLoadGameFP ?
9097                 gameNumber == lastLoadGameNumber + 1 :
9098                 gameNumber == 1) {
9099                 gn = 1;
9100             } else {
9101                 DisplayError(_("Can't seek on game file"), 0);
9102                 return FALSE;
9103             }
9104         }
9105     }
9106     lastLoadGameFP = f;
9107     lastLoadGameNumber = gameNumber;
9108     strcpy(lastLoadGameTitle, title);
9109     lastLoadGameUseList = useList;
9110
9111     yynewfile(f);
9112
9113     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9114       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9115                 lg->gameInfo.black);
9116             DisplayTitle(buf);
9117     } else if (*title != NULLCHAR) {
9118         if (gameNumber > 1) {
9119             sprintf(buf, "%s %d", title, gameNumber);
9120             DisplayTitle(buf);
9121         } else {
9122             DisplayTitle(title);
9123         }
9124     }
9125
9126     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9127         gameMode = PlayFromGameFile;
9128         ModeHighlight();
9129     }
9130
9131     currentMove = forwardMostMove = backwardMostMove = 0;
9132     CopyBoard(boards[0], initialPosition);
9133     StopClocks();
9134
9135     /*
9136      * Skip the first gn-1 games in the file.
9137      * Also skip over anything that precedes an identifiable 
9138      * start of game marker, to avoid being confused by 
9139      * garbage at the start of the file.  Currently 
9140      * recognized start of game markers are the move number "1",
9141      * the pattern "gnuchess .* game", the pattern
9142      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9143      * A game that starts with one of the latter two patterns
9144      * will also have a move number 1, possibly
9145      * following a position diagram.
9146      * 5-4-02: Let's try being more lenient and allowing a game to
9147      * start with an unnumbered move.  Does that break anything?
9148      */
9149     cm = lastLoadGameStart = (ChessMove) 0;
9150     while (gn > 0) {
9151         yyboardindex = forwardMostMove;
9152         cm = (ChessMove) yylex();
9153         switch (cm) {
9154           case (ChessMove) 0:
9155             if (cmailMsgLoaded) {
9156                 nCmailGames = CMAIL_MAX_GAMES - gn;
9157             } else {
9158                 Reset(TRUE, TRUE);
9159                 DisplayError(_("Game not found in file"), 0);
9160             }
9161             return FALSE;
9162
9163           case GNUChessGame:
9164           case XBoardGame:
9165             gn--;
9166             lastLoadGameStart = cm;
9167             break;
9168             
9169           case MoveNumberOne:
9170             switch (lastLoadGameStart) {
9171               case GNUChessGame:
9172               case XBoardGame:
9173               case PGNTag:
9174                 break;
9175               case MoveNumberOne:
9176               case (ChessMove) 0:
9177                 gn--;           /* count this game */
9178                 lastLoadGameStart = cm;
9179                 break;
9180               default:
9181                 /* impossible */
9182                 break;
9183             }
9184             break;
9185
9186           case PGNTag:
9187             switch (lastLoadGameStart) {
9188               case GNUChessGame:
9189               case PGNTag:
9190               case MoveNumberOne:
9191               case (ChessMove) 0:
9192                 gn--;           /* count this game */
9193                 lastLoadGameStart = cm;
9194                 break;
9195               case XBoardGame:
9196                 lastLoadGameStart = cm; /* game counted already */
9197                 break;
9198               default:
9199                 /* impossible */
9200                 break;
9201             }
9202             if (gn > 0) {
9203                 do {
9204                     yyboardindex = forwardMostMove;
9205                     cm = (ChessMove) yylex();
9206                 } while (cm == PGNTag || cm == Comment);
9207             }
9208             break;
9209
9210           case WhiteWins:
9211           case BlackWins:
9212           case GameIsDrawn:
9213             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9214                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9215                     != CMAIL_OLD_RESULT) {
9216                     nCmailResults ++ ;
9217                     cmailResult[  CMAIL_MAX_GAMES
9218                                 - gn - 1] = CMAIL_OLD_RESULT;
9219                 }
9220             }
9221             break;
9222
9223           case NormalMove:
9224             /* Only a NormalMove can be at the start of a game
9225              * without a position diagram. */
9226             if (lastLoadGameStart == (ChessMove) 0) {
9227               gn--;
9228               lastLoadGameStart = MoveNumberOne;
9229             }
9230             break;
9231
9232           default:
9233             break;
9234         }
9235     }
9236     
9237     if (appData.debugMode)
9238       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9239
9240     if (cm == XBoardGame) {
9241         /* Skip any header junk before position diagram and/or move 1 */
9242         for (;;) {
9243             yyboardindex = forwardMostMove;
9244             cm = (ChessMove) yylex();
9245
9246             if (cm == (ChessMove) 0 ||
9247                 cm == GNUChessGame || cm == XBoardGame) {
9248                 /* Empty game; pretend end-of-file and handle later */
9249                 cm = (ChessMove) 0;
9250                 break;
9251             }
9252
9253             if (cm == MoveNumberOne || cm == PositionDiagram ||
9254                 cm == PGNTag || cm == Comment)
9255               break;
9256         }
9257     } else if (cm == GNUChessGame) {
9258         if (gameInfo.event != NULL) {
9259             free(gameInfo.event);
9260         }
9261         gameInfo.event = StrSave(yy_text);
9262     }   
9263
9264     startedFromSetupPosition = FALSE;
9265     while (cm == PGNTag) {
9266         if (appData.debugMode) 
9267           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9268         err = ParsePGNTag(yy_text, &gameInfo);
9269         if (!err) numPGNTags++;
9270
9271         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9272         if(gameInfo.variant != oldVariant) {
9273             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9274             InitPosition(TRUE);
9275             oldVariant = gameInfo.variant;
9276             if (appData.debugMode) 
9277               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9278         }
9279
9280
9281         if (gameInfo.fen != NULL) {
9282           Board initial_position;
9283           startedFromSetupPosition = TRUE;
9284           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9285             Reset(TRUE, TRUE);
9286             DisplayError(_("Bad FEN position in file"), 0);
9287             return FALSE;
9288           }
9289           CopyBoard(boards[0], initial_position);
9290           if (blackPlaysFirst) {
9291             currentMove = forwardMostMove = backwardMostMove = 1;
9292             CopyBoard(boards[1], initial_position);
9293             strcpy(moveList[0], "");
9294             strcpy(parseList[0], "");
9295             timeRemaining[0][1] = whiteTimeRemaining;
9296             timeRemaining[1][1] = blackTimeRemaining;
9297             if (commentList[0] != NULL) {
9298               commentList[1] = commentList[0];
9299               commentList[0] = NULL;
9300             }
9301           } else {
9302             currentMove = forwardMostMove = backwardMostMove = 0;
9303           }
9304           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9305           {   int i;
9306               initialRulePlies = FENrulePlies;
9307               epStatus[forwardMostMove] = FENepStatus;
9308               for( i=0; i< nrCastlingRights; i++ )
9309                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9310           }
9311           yyboardindex = forwardMostMove;
9312           free(gameInfo.fen);
9313           gameInfo.fen = NULL;
9314         }
9315
9316         yyboardindex = forwardMostMove;
9317         cm = (ChessMove) yylex();
9318
9319         /* Handle comments interspersed among the tags */
9320         while (cm == Comment) {
9321             char *p;
9322             if (appData.debugMode) 
9323               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9324             p = yy_text;
9325             if (*p == '{' || *p == '[' || *p == '(') {
9326                 p[strlen(p) - 1] = NULLCHAR;
9327                 p++;
9328             }
9329             while (*p == '\n') p++;
9330             AppendComment(currentMove, p);
9331             yyboardindex = forwardMostMove;
9332             cm = (ChessMove) yylex();
9333         }
9334     }
9335
9336     /* don't rely on existence of Event tag since if game was
9337      * pasted from clipboard the Event tag may not exist
9338      */
9339     if (numPGNTags > 0){
9340         char *tags;
9341         if (gameInfo.variant == VariantNormal) {
9342           gameInfo.variant = StringToVariant(gameInfo.event);
9343         }
9344         if (!matchMode) {
9345           if( appData.autoDisplayTags ) {
9346             tags = PGNTags(&gameInfo);
9347             TagsPopUp(tags, CmailMsg());
9348             free(tags);
9349           }
9350         }
9351     } else {
9352         /* Make something up, but don't display it now */
9353         SetGameInfo();
9354         TagsPopDown();
9355     }
9356
9357     if (cm == PositionDiagram) {
9358         int i, j;
9359         char *p;
9360         Board initial_position;
9361
9362         if (appData.debugMode)
9363           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9364
9365         if (!startedFromSetupPosition) {
9366             p = yy_text;
9367             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9368               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9369                 switch (*p) {
9370                   case '[':
9371                   case '-':
9372                   case ' ':
9373                   case '\t':
9374                   case '\n':
9375                   case '\r':
9376                     break;
9377                   default:
9378                     initial_position[i][j++] = CharToPiece(*p);
9379                     break;
9380                 }
9381             while (*p == ' ' || *p == '\t' ||
9382                    *p == '\n' || *p == '\r') p++;
9383         
9384             if (strncmp(p, "black", strlen("black"))==0)
9385               blackPlaysFirst = TRUE;
9386             else
9387               blackPlaysFirst = FALSE;
9388             startedFromSetupPosition = TRUE;
9389         
9390             CopyBoard(boards[0], initial_position);
9391             if (blackPlaysFirst) {
9392                 currentMove = forwardMostMove = backwardMostMove = 1;
9393                 CopyBoard(boards[1], initial_position);
9394                 strcpy(moveList[0], "");
9395                 strcpy(parseList[0], "");
9396                 timeRemaining[0][1] = whiteTimeRemaining;
9397                 timeRemaining[1][1] = blackTimeRemaining;
9398                 if (commentList[0] != NULL) {
9399                     commentList[1] = commentList[0];
9400                     commentList[0] = NULL;
9401                 }
9402             } else {
9403                 currentMove = forwardMostMove = backwardMostMove = 0;
9404             }
9405         }
9406         yyboardindex = forwardMostMove;
9407         cm = (ChessMove) yylex();
9408     }
9409
9410     if (first.pr == NoProc) {
9411         StartChessProgram(&first);
9412     }
9413     InitChessProgram(&first, FALSE);
9414     SendToProgram("force\n", &first);
9415     if (startedFromSetupPosition) {
9416         SendBoard(&first, forwardMostMove);
9417     if (appData.debugMode) {
9418         fprintf(debugFP, "Load Game\n");
9419     }
9420         DisplayBothClocks();
9421     }      
9422
9423     /* [HGM] server: flag to write setup moves in broadcast file as one */
9424     loadFlag = appData.suppressLoadMoves;
9425
9426     while (cm == Comment) {
9427         char *p;
9428         if (appData.debugMode) 
9429           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9430         p = yy_text;
9431         if (*p == '{' || *p == '[' || *p == '(') {
9432             p[strlen(p) - 1] = NULLCHAR;
9433             p++;
9434         }
9435         while (*p == '\n') p++;
9436         AppendComment(currentMove, p);
9437         yyboardindex = forwardMostMove;
9438         cm = (ChessMove) yylex();
9439     }
9440
9441     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9442         cm == WhiteWins || cm == BlackWins ||
9443         cm == GameIsDrawn || cm == GameUnfinished) {
9444         DisplayMessage("", _("No moves in game"));
9445         if (cmailMsgLoaded) {
9446             if (appData.debugMode)
9447               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9448             ClearHighlights();
9449             flipView = FALSE;
9450         }
9451         DrawPosition(FALSE, boards[currentMove]);
9452         DisplayBothClocks();
9453         gameMode = EditGame;
9454         ModeHighlight();
9455         gameFileFP = NULL;
9456         cmailOldMove = 0;
9457         return TRUE;
9458     }
9459
9460     // [HGM] PV info: routine tests if comment empty
9461     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9462         DisplayComment(currentMove - 1, commentList[currentMove]);
9463     }
9464     if (!matchMode && appData.timeDelay != 0) 
9465       DrawPosition(FALSE, boards[currentMove]);
9466
9467     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9468       programStats.ok_to_send = 1;
9469     }
9470
9471     /* if the first token after the PGN tags is a move
9472      * and not move number 1, retrieve it from the parser 
9473      */
9474     if (cm != MoveNumberOne)
9475         LoadGameOneMove(cm);
9476
9477     /* load the remaining moves from the file */
9478     while (LoadGameOneMove((ChessMove)0)) {
9479       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9480       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9481     }
9482
9483     /* rewind to the start of the game */
9484     currentMove = backwardMostMove;
9485
9486     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9487
9488     if (oldGameMode == AnalyzeFile ||
9489         oldGameMode == AnalyzeMode) {
9490       AnalyzeFileEvent();
9491     }
9492
9493     if (matchMode || appData.timeDelay == 0) {
9494       ToEndEvent();
9495       gameMode = EditGame;
9496       ModeHighlight();
9497     } else if (appData.timeDelay > 0) {
9498       AutoPlayGameLoop();
9499     }
9500
9501     if (appData.debugMode) 
9502         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9503
9504     loadFlag = 0; /* [HGM] true game starts */
9505     return TRUE;
9506 }
9507
9508 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9509 int
9510 ReloadPosition(offset)
9511      int offset;
9512 {
9513     int positionNumber = lastLoadPositionNumber + offset;
9514     if (lastLoadPositionFP == NULL) {
9515         DisplayError(_("No position has been loaded yet"), 0);
9516         return FALSE;
9517     }
9518     if (positionNumber <= 0) {
9519         DisplayError(_("Can't back up any further"), 0);
9520         return FALSE;
9521     }
9522     return LoadPosition(lastLoadPositionFP, positionNumber,
9523                         lastLoadPositionTitle);
9524 }
9525
9526 /* Load the nth position from the given file */
9527 int
9528 LoadPositionFromFile(filename, n, title)
9529      char *filename;
9530      int n;
9531      char *title;
9532 {
9533     FILE *f;
9534     char buf[MSG_SIZ];
9535
9536     if (strcmp(filename, "-") == 0) {
9537         return LoadPosition(stdin, n, "stdin");
9538     } else {
9539         f = fopen(filename, "rb");
9540         if (f == NULL) {
9541             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9542             DisplayError(buf, errno);
9543             return FALSE;
9544         } else {
9545             return LoadPosition(f, n, title);
9546         }
9547     }
9548 }
9549
9550 /* Load the nth position from the given open file, and close it */
9551 int
9552 LoadPosition(f, positionNumber, title)
9553      FILE *f;
9554      int positionNumber;
9555      char *title;
9556 {
9557     char *p, line[MSG_SIZ];
9558     Board initial_position;
9559     int i, j, fenMode, pn;
9560     
9561     if (gameMode == Training )
9562         SetTrainingModeOff();
9563
9564     if (gameMode != BeginningOfGame) {
9565         Reset(FALSE, TRUE);
9566     }
9567     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9568         fclose(lastLoadPositionFP);
9569     }
9570     if (positionNumber == 0) positionNumber = 1;
9571     lastLoadPositionFP = f;
9572     lastLoadPositionNumber = positionNumber;
9573     strcpy(lastLoadPositionTitle, title);
9574     if (first.pr == NoProc) {
9575       StartChessProgram(&first);
9576       InitChessProgram(&first, FALSE);
9577     }    
9578     pn = positionNumber;
9579     if (positionNumber < 0) {
9580         /* Negative position number means to seek to that byte offset */
9581         if (fseek(f, -positionNumber, 0) == -1) {
9582             DisplayError(_("Can't seek on position file"), 0);
9583             return FALSE;
9584         };
9585         pn = 1;
9586     } else {
9587         if (fseek(f, 0, 0) == -1) {
9588             if (f == lastLoadPositionFP ?
9589                 positionNumber == lastLoadPositionNumber + 1 :
9590                 positionNumber == 1) {
9591                 pn = 1;
9592             } else {
9593                 DisplayError(_("Can't seek on position file"), 0);
9594                 return FALSE;
9595             }
9596         }
9597     }
9598     /* See if this file is FEN or old-style xboard */
9599     if (fgets(line, MSG_SIZ, f) == NULL) {
9600         DisplayError(_("Position not found in file"), 0);
9601         return FALSE;
9602     }
9603     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9604     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9605
9606     if (pn >= 2) {
9607         if (fenMode || line[0] == '#') pn--;
9608         while (pn > 0) {
9609             /* skip positions before number pn */
9610             if (fgets(line, MSG_SIZ, f) == NULL) {
9611                 Reset(TRUE, TRUE);
9612                 DisplayError(_("Position not found in file"), 0);
9613                 return FALSE;
9614             }
9615             if (fenMode || line[0] == '#') pn--;
9616         }
9617     }
9618
9619     if (fenMode) {
9620         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9621             DisplayError(_("Bad FEN position in file"), 0);
9622             return FALSE;
9623         }
9624     } else {
9625         (void) fgets(line, MSG_SIZ, f);
9626         (void) fgets(line, MSG_SIZ, f);
9627     
9628         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9629             (void) fgets(line, MSG_SIZ, f);
9630             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9631                 if (*p == ' ')
9632                   continue;
9633                 initial_position[i][j++] = CharToPiece(*p);
9634             }
9635         }
9636     
9637         blackPlaysFirst = FALSE;
9638         if (!feof(f)) {
9639             (void) fgets(line, MSG_SIZ, f);
9640             if (strncmp(line, "black", strlen("black"))==0)
9641               blackPlaysFirst = TRUE;
9642         }
9643     }
9644     startedFromSetupPosition = TRUE;
9645     
9646     SendToProgram("force\n", &first);
9647     CopyBoard(boards[0], initial_position);
9648     if (blackPlaysFirst) {
9649         currentMove = forwardMostMove = backwardMostMove = 1;
9650         strcpy(moveList[0], "");
9651         strcpy(parseList[0], "");
9652         CopyBoard(boards[1], initial_position);
9653         DisplayMessage("", _("Black to play"));
9654     } else {
9655         currentMove = forwardMostMove = backwardMostMove = 0;
9656         DisplayMessage("", _("White to play"));
9657     }
9658           /* [HGM] copy FEN attributes as well */
9659           {   int i;
9660               initialRulePlies = FENrulePlies;
9661               epStatus[forwardMostMove] = FENepStatus;
9662               for( i=0; i< nrCastlingRights; i++ )
9663                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9664           }
9665     SendBoard(&first, forwardMostMove);
9666     if (appData.debugMode) {
9667 int i, j;
9668   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9669   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9670         fprintf(debugFP, "Load Position\n");
9671     }
9672
9673     if (positionNumber > 1) {
9674         sprintf(line, "%s %d", title, positionNumber);
9675         DisplayTitle(line);
9676     } else {
9677         DisplayTitle(title);
9678     }
9679     gameMode = EditGame;
9680     ModeHighlight();
9681     ResetClocks();
9682     timeRemaining[0][1] = whiteTimeRemaining;
9683     timeRemaining[1][1] = blackTimeRemaining;
9684     DrawPosition(FALSE, boards[currentMove]);
9685    
9686     return TRUE;
9687 }
9688
9689
9690 void
9691 CopyPlayerNameIntoFileName(dest, src)
9692      char **dest, *src;
9693 {
9694     while (*src != NULLCHAR && *src != ',') {
9695         if (*src == ' ') {
9696             *(*dest)++ = '_';
9697             src++;
9698         } else {
9699             *(*dest)++ = *src++;
9700         }
9701     }
9702 }
9703
9704 char *DefaultFileName(ext)
9705      char *ext;
9706 {
9707     static char def[MSG_SIZ];
9708     char *p;
9709
9710     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9711         p = def;
9712         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9713         *p++ = '-';
9714         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9715         *p++ = '.';
9716         strcpy(p, ext);
9717     } else {
9718         def[0] = NULLCHAR;
9719     }
9720     return def;
9721 }
9722
9723 /* Save the current game to the given file */
9724 int
9725 SaveGameToFile(filename, append)
9726      char *filename;
9727      int append;
9728 {
9729     FILE *f;
9730     char buf[MSG_SIZ];
9731
9732     if (strcmp(filename, "-") == 0) {
9733         return SaveGame(stdout, 0, NULL);
9734     } else {
9735         f = fopen(filename, append ? "a" : "w");
9736         if (f == NULL) {
9737             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9738             DisplayError(buf, errno);
9739             return FALSE;
9740         } else {
9741             return SaveGame(f, 0, NULL);
9742         }
9743     }
9744 }
9745
9746 char *
9747 SavePart(str)
9748      char *str;
9749 {
9750     static char buf[MSG_SIZ];
9751     char *p;
9752     
9753     p = strchr(str, ' ');
9754     if (p == NULL) return str;
9755     strncpy(buf, str, p - str);
9756     buf[p - str] = NULLCHAR;
9757     return buf;
9758 }
9759
9760 #define PGN_MAX_LINE 75
9761
9762 #define PGN_SIDE_WHITE  0
9763 #define PGN_SIDE_BLACK  1
9764
9765 /* [AS] */
9766 static int FindFirstMoveOutOfBook( int side )
9767 {
9768     int result = -1;
9769
9770     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9771         int index = backwardMostMove;
9772         int has_book_hit = 0;
9773
9774         if( (index % 2) != side ) {
9775             index++;
9776         }
9777
9778         while( index < forwardMostMove ) {
9779             /* Check to see if engine is in book */
9780             int depth = pvInfoList[index].depth;
9781             int score = pvInfoList[index].score;
9782             int in_book = 0;
9783
9784             if( depth <= 2 ) {
9785                 in_book = 1;
9786             }
9787             else if( score == 0 && depth == 63 ) {
9788                 in_book = 1; /* Zappa */
9789             }
9790             else if( score == 2 && depth == 99 ) {
9791                 in_book = 1; /* Abrok */
9792             }
9793
9794             has_book_hit += in_book;
9795
9796             if( ! in_book ) {
9797                 result = index;
9798
9799                 break;
9800             }
9801
9802             index += 2;
9803         }
9804     }
9805
9806     return result;
9807 }
9808
9809 /* [AS] */
9810 void GetOutOfBookInfo( char * buf )
9811 {
9812     int oob[2];
9813     int i;
9814     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9815
9816     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9817     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9818
9819     *buf = '\0';
9820
9821     if( oob[0] >= 0 || oob[1] >= 0 ) {
9822         for( i=0; i<2; i++ ) {
9823             int idx = oob[i];
9824
9825             if( idx >= 0 ) {
9826                 if( i > 0 && oob[0] >= 0 ) {
9827                     strcat( buf, "   " );
9828                 }
9829
9830                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9831                 sprintf( buf+strlen(buf), "%s%.2f", 
9832                     pvInfoList[idx].score >= 0 ? "+" : "",
9833                     pvInfoList[idx].score / 100.0 );
9834             }
9835         }
9836     }
9837 }
9838
9839 /* Save game in PGN style and close the file */
9840 int
9841 SaveGamePGN(f)
9842      FILE *f;
9843 {
9844     int i, offset, linelen, newblock;
9845     time_t tm;
9846 //    char *movetext;
9847     char numtext[32];
9848     int movelen, numlen, blank;
9849     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9850
9851     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9852     
9853     tm = time((time_t *) NULL);
9854     
9855     PrintPGNTags(f, &gameInfo);
9856     
9857     if (backwardMostMove > 0 || startedFromSetupPosition) {
9858         char *fen = PositionToFEN(backwardMostMove, NULL);
9859         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9860         fprintf(f, "\n{--------------\n");
9861         PrintPosition(f, backwardMostMove);
9862         fprintf(f, "--------------}\n");
9863         free(fen);
9864     }
9865     else {
9866         /* [AS] Out of book annotation */
9867         if( appData.saveOutOfBookInfo ) {
9868             char buf[64];
9869
9870             GetOutOfBookInfo( buf );
9871
9872             if( buf[0] != '\0' ) {
9873                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9874             }
9875         }
9876
9877         fprintf(f, "\n");
9878     }
9879
9880     i = backwardMostMove;
9881     linelen = 0;
9882     newblock = TRUE;
9883
9884     while (i < forwardMostMove) {
9885         /* Print comments preceding this move */
9886         if (commentList[i] != NULL) {
9887             if (linelen > 0) fprintf(f, "\n");
9888             fprintf(f, "{\n%s}\n", commentList[i]);
9889             linelen = 0;
9890             newblock = TRUE;
9891         }
9892
9893         /* Format move number */
9894         if ((i % 2) == 0) {
9895             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9896         } else {
9897             if (newblock) {
9898                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9899             } else {
9900                 numtext[0] = NULLCHAR;
9901             }
9902         }
9903         numlen = strlen(numtext);
9904         newblock = FALSE;
9905
9906         /* Print move number */
9907         blank = linelen > 0 && numlen > 0;
9908         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9909             fprintf(f, "\n");
9910             linelen = 0;
9911             blank = 0;
9912         }
9913         if (blank) {
9914             fprintf(f, " ");
9915             linelen++;
9916         }
9917         fprintf(f, "%s", numtext);
9918         linelen += numlen;
9919
9920         /* Get move */
9921         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9922         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9923
9924         /* Print move */
9925         blank = linelen > 0 && movelen > 0;
9926         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9927             fprintf(f, "\n");
9928             linelen = 0;
9929             blank = 0;
9930         }
9931         if (blank) {
9932             fprintf(f, " ");
9933             linelen++;
9934         }
9935         fprintf(f, "%s", move_buffer);
9936         linelen += movelen;
9937
9938         /* [AS] Add PV info if present */
9939         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9940             /* [HGM] add time */
9941             char buf[MSG_SIZ]; int seconds = 0;
9942
9943             if(i >= backwardMostMove) {
9944                 if(WhiteOnMove(i))
9945                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9946                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9947                 else
9948                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9949                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9950             }
9951             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9952
9953             if( seconds <= 0) buf[0] = 0; else
9954             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9955                 seconds = (seconds + 4)/10; // round to full seconds
9956                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9957                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9958             }
9959
9960             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9961                 pvInfoList[i].score >= 0 ? "+" : "",
9962                 pvInfoList[i].score / 100.0,
9963                 pvInfoList[i].depth,
9964                 buf );
9965
9966             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9967
9968             /* Print score/depth */
9969             blank = linelen > 0 && movelen > 0;
9970             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9971                 fprintf(f, "\n");
9972                 linelen = 0;
9973                 blank = 0;
9974             }
9975             if (blank) {
9976                 fprintf(f, " ");
9977                 linelen++;
9978             }
9979             fprintf(f, "%s", move_buffer);
9980             linelen += movelen;
9981         }
9982
9983         i++;
9984     }
9985     
9986     /* Start a new line */
9987     if (linelen > 0) fprintf(f, "\n");
9988
9989     /* Print comments after last move */
9990     if (commentList[i] != NULL) {
9991         fprintf(f, "{\n%s}\n", commentList[i]);
9992     }
9993
9994     /* Print result */
9995     if (gameInfo.resultDetails != NULL &&
9996         gameInfo.resultDetails[0] != NULLCHAR) {
9997         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9998                 PGNResult(gameInfo.result));
9999     } else {
10000         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10001     }
10002
10003     fclose(f);
10004     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10005     return TRUE;
10006 }
10007
10008 /* Save game in old style and close the file */
10009 int
10010 SaveGameOldStyle(f)
10011      FILE *f;
10012 {
10013     int i, offset;
10014     time_t tm;
10015     
10016     tm = time((time_t *) NULL);
10017     
10018     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10019     PrintOpponents(f);
10020     
10021     if (backwardMostMove > 0 || startedFromSetupPosition) {
10022         fprintf(f, "\n[--------------\n");
10023         PrintPosition(f, backwardMostMove);
10024         fprintf(f, "--------------]\n");
10025     } else {
10026         fprintf(f, "\n");
10027     }
10028
10029     i = backwardMostMove;
10030     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10031
10032     while (i < forwardMostMove) {
10033         if (commentList[i] != NULL) {
10034             fprintf(f, "[%s]\n", commentList[i]);
10035         }
10036
10037         if ((i % 2) == 1) {
10038             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10039             i++;
10040         } else {
10041             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10042             i++;
10043             if (commentList[i] != NULL) {
10044                 fprintf(f, "\n");
10045                 continue;
10046             }
10047             if (i >= forwardMostMove) {
10048                 fprintf(f, "\n");
10049                 break;
10050             }
10051             fprintf(f, "%s\n", parseList[i]);
10052             i++;
10053         }
10054     }
10055     
10056     if (commentList[i] != NULL) {
10057         fprintf(f, "[%s]\n", commentList[i]);
10058     }
10059
10060     /* This isn't really the old style, but it's close enough */
10061     if (gameInfo.resultDetails != NULL &&
10062         gameInfo.resultDetails[0] != NULLCHAR) {
10063         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10064                 gameInfo.resultDetails);
10065     } else {
10066         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10067     }
10068
10069     fclose(f);
10070     return TRUE;
10071 }
10072
10073 /* Save the current game to open file f and close the file */
10074 int
10075 SaveGame(f, dummy, dummy2)
10076      FILE *f;
10077      int dummy;
10078      char *dummy2;
10079 {
10080     if (gameMode == EditPosition) EditPositionDone();
10081     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10082     if (appData.oldSaveStyle)
10083       return SaveGameOldStyle(f);
10084     else
10085       return SaveGamePGN(f);
10086 }
10087
10088 /* Save the current position to the given file */
10089 int
10090 SavePositionToFile(filename)
10091      char *filename;
10092 {
10093     FILE *f;
10094     char buf[MSG_SIZ];
10095
10096     if (strcmp(filename, "-") == 0) {
10097         return SavePosition(stdout, 0, NULL);
10098     } else {
10099         f = fopen(filename, "a");
10100         if (f == NULL) {
10101             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10102             DisplayError(buf, errno);
10103             return FALSE;
10104         } else {
10105             SavePosition(f, 0, NULL);
10106             return TRUE;
10107         }
10108     }
10109 }
10110
10111 /* Save the current position to the given open file and close the file */
10112 int
10113 SavePosition(f, dummy, dummy2)
10114      FILE *f;
10115      int dummy;
10116      char *dummy2;
10117 {
10118     time_t tm;
10119     char *fen;
10120     
10121     if (appData.oldSaveStyle) {
10122         tm = time((time_t *) NULL);
10123     
10124         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10125         PrintOpponents(f);
10126         fprintf(f, "[--------------\n");
10127         PrintPosition(f, currentMove);
10128         fprintf(f, "--------------]\n");
10129     } else {
10130         fen = PositionToFEN(currentMove, NULL);
10131         fprintf(f, "%s\n", fen);
10132         free(fen);
10133     }
10134     fclose(f);
10135     return TRUE;
10136 }
10137
10138 void
10139 ReloadCmailMsgEvent(unregister)
10140      int unregister;
10141 {
10142 #if !WIN32
10143     static char *inFilename = NULL;
10144     static char *outFilename;
10145     int i;
10146     struct stat inbuf, outbuf;
10147     int status;
10148     
10149     /* Any registered moves are unregistered if unregister is set, */
10150     /* i.e. invoked by the signal handler */
10151     if (unregister) {
10152         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10153             cmailMoveRegistered[i] = FALSE;
10154             if (cmailCommentList[i] != NULL) {
10155                 free(cmailCommentList[i]);
10156                 cmailCommentList[i] = NULL;
10157             }
10158         }
10159         nCmailMovesRegistered = 0;
10160     }
10161
10162     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10163         cmailResult[i] = CMAIL_NOT_RESULT;
10164     }
10165     nCmailResults = 0;
10166
10167     if (inFilename == NULL) {
10168         /* Because the filenames are static they only get malloced once  */
10169         /* and they never get freed                                      */
10170         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10171         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10172
10173         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10174         sprintf(outFilename, "%s.out", appData.cmailGameName);
10175     }
10176     
10177     status = stat(outFilename, &outbuf);
10178     if (status < 0) {
10179         cmailMailedMove = FALSE;
10180     } else {
10181         status = stat(inFilename, &inbuf);
10182         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10183     }
10184     
10185     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10186        counts the games, notes how each one terminated, etc.
10187        
10188        It would be nice to remove this kludge and instead gather all
10189        the information while building the game list.  (And to keep it
10190        in the game list nodes instead of having a bunch of fixed-size
10191        parallel arrays.)  Note this will require getting each game's
10192        termination from the PGN tags, as the game list builder does
10193        not process the game moves.  --mann
10194        */
10195     cmailMsgLoaded = TRUE;
10196     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10197     
10198     /* Load first game in the file or popup game menu */
10199     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10200
10201 #endif /* !WIN32 */
10202     return;
10203 }
10204
10205 int
10206 RegisterMove()
10207 {
10208     FILE *f;
10209     char string[MSG_SIZ];
10210
10211     if (   cmailMailedMove
10212         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10213         return TRUE;            /* Allow free viewing  */
10214     }
10215
10216     /* Unregister move to ensure that we don't leave RegisterMove        */
10217     /* with the move registered when the conditions for registering no   */
10218     /* longer hold                                                       */
10219     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10220         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10221         nCmailMovesRegistered --;
10222
10223         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10224           {
10225               free(cmailCommentList[lastLoadGameNumber - 1]);
10226               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10227           }
10228     }
10229
10230     if (cmailOldMove == -1) {
10231         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10232         return FALSE;
10233     }
10234
10235     if (currentMove > cmailOldMove + 1) {
10236         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10237         return FALSE;
10238     }
10239
10240     if (currentMove < cmailOldMove) {
10241         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10242         return FALSE;
10243     }
10244
10245     if (forwardMostMove > currentMove) {
10246         /* Silently truncate extra moves */
10247         TruncateGame();
10248     }
10249
10250     if (   (currentMove == cmailOldMove + 1)
10251         || (   (currentMove == cmailOldMove)
10252             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10253                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10254         if (gameInfo.result != GameUnfinished) {
10255             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10256         }
10257
10258         if (commentList[currentMove] != NULL) {
10259             cmailCommentList[lastLoadGameNumber - 1]
10260               = StrSave(commentList[currentMove]);
10261         }
10262         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10263
10264         if (appData.debugMode)
10265           fprintf(debugFP, "Saving %s for game %d\n",
10266                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10267
10268         sprintf(string,
10269                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10270         
10271         f = fopen(string, "w");
10272         if (appData.oldSaveStyle) {
10273             SaveGameOldStyle(f); /* also closes the file */
10274             
10275             sprintf(string, "%s.pos.out", appData.cmailGameName);
10276             f = fopen(string, "w");
10277             SavePosition(f, 0, NULL); /* also closes the file */
10278         } else {
10279             fprintf(f, "{--------------\n");
10280             PrintPosition(f, currentMove);
10281             fprintf(f, "--------------}\n\n");
10282             
10283             SaveGame(f, 0, NULL); /* also closes the file*/
10284         }
10285         
10286         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10287         nCmailMovesRegistered ++;
10288     } else if (nCmailGames == 1) {
10289         DisplayError(_("You have not made a move yet"), 0);
10290         return FALSE;
10291     }
10292
10293     return TRUE;
10294 }
10295
10296 void
10297 MailMoveEvent()
10298 {
10299 #if !WIN32
10300     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10301     FILE *commandOutput;
10302     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10303     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10304     int nBuffers;
10305     int i;
10306     int archived;
10307     char *arcDir;
10308
10309     if (! cmailMsgLoaded) {
10310         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10311         return;
10312     }
10313
10314     if (nCmailGames == nCmailResults) {
10315         DisplayError(_("No unfinished games"), 0);
10316         return;
10317     }
10318
10319 #if CMAIL_PROHIBIT_REMAIL
10320     if (cmailMailedMove) {
10321         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);
10322         DisplayError(msg, 0);
10323         return;
10324     }
10325 #endif
10326
10327     if (! (cmailMailedMove || RegisterMove())) return;
10328     
10329     if (   cmailMailedMove
10330         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10331         sprintf(string, partCommandString,
10332                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10333         commandOutput = popen(string, "r");
10334
10335         if (commandOutput == NULL) {
10336             DisplayError(_("Failed to invoke cmail"), 0);
10337         } else {
10338             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10339                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10340             }
10341             if (nBuffers > 1) {
10342                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10343                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10344                 nBytes = MSG_SIZ - 1;
10345             } else {
10346                 (void) memcpy(msg, buffer, nBytes);
10347             }
10348             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10349
10350             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10351                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10352
10353                 archived = TRUE;
10354                 for (i = 0; i < nCmailGames; i ++) {
10355                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10356                         archived = FALSE;
10357                     }
10358                 }
10359                 if (   archived
10360                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10361                         != NULL)) {
10362                     sprintf(buffer, "%s/%s.%s.archive",
10363                             arcDir,
10364                             appData.cmailGameName,
10365                             gameInfo.date);
10366                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10367                     cmailMsgLoaded = FALSE;
10368                 }
10369             }
10370
10371             DisplayInformation(msg);
10372             pclose(commandOutput);
10373         }
10374     } else {
10375         if ((*cmailMsg) != '\0') {
10376             DisplayInformation(cmailMsg);
10377         }
10378     }
10379
10380     return;
10381 #endif /* !WIN32 */
10382 }
10383
10384 char *
10385 CmailMsg()
10386 {
10387 #if WIN32
10388     return NULL;
10389 #else
10390     int  prependComma = 0;
10391     char number[5];
10392     char string[MSG_SIZ];       /* Space for game-list */
10393     int  i;
10394     
10395     if (!cmailMsgLoaded) return "";
10396
10397     if (cmailMailedMove) {
10398         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10399     } else {
10400         /* Create a list of games left */
10401         sprintf(string, "[");
10402         for (i = 0; i < nCmailGames; i ++) {
10403             if (! (   cmailMoveRegistered[i]
10404                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10405                 if (prependComma) {
10406                     sprintf(number, ",%d", i + 1);
10407                 } else {
10408                     sprintf(number, "%d", i + 1);
10409                     prependComma = 1;
10410                 }
10411                 
10412                 strcat(string, number);
10413             }
10414         }
10415         strcat(string, "]");
10416
10417         if (nCmailMovesRegistered + nCmailResults == 0) {
10418             switch (nCmailGames) {
10419               case 1:
10420                 sprintf(cmailMsg,
10421                         _("Still need to make move for game\n"));
10422                 break;
10423                 
10424               case 2:
10425                 sprintf(cmailMsg,
10426                         _("Still need to make moves for both games\n"));
10427                 break;
10428                 
10429               default:
10430                 sprintf(cmailMsg,
10431                         _("Still need to make moves for all %d games\n"),
10432                         nCmailGames);
10433                 break;
10434             }
10435         } else {
10436             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10437               case 1:
10438                 sprintf(cmailMsg,
10439                         _("Still need to make a move for game %s\n"),
10440                         string);
10441                 break;
10442                 
10443               case 0:
10444                 if (nCmailResults == nCmailGames) {
10445                     sprintf(cmailMsg, _("No unfinished games\n"));
10446                 } else {
10447                     sprintf(cmailMsg, _("Ready to send mail\n"));
10448                 }
10449                 break;
10450                 
10451               default:
10452                 sprintf(cmailMsg,
10453                         _("Still need to make moves for games %s\n"),
10454                         string);
10455             }
10456         }
10457     }
10458     return cmailMsg;
10459 #endif /* WIN32 */
10460 }
10461
10462 void
10463 ResetGameEvent()
10464 {
10465     if (gameMode == Training)
10466       SetTrainingModeOff();
10467
10468     Reset(TRUE, TRUE);
10469     cmailMsgLoaded = FALSE;
10470     if (appData.icsActive) {
10471       SendToICS(ics_prefix);
10472       SendToICS("refresh\n");
10473     }
10474 }
10475
10476 void
10477 ExitEvent(status)
10478      int status;
10479 {
10480     exiting++;
10481     if (exiting > 2) {
10482       /* Give up on clean exit */
10483       exit(status);
10484     }
10485     if (exiting > 1) {
10486       /* Keep trying for clean exit */
10487       return;
10488     }
10489
10490     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10491
10492     if (telnetISR != NULL) {
10493       RemoveInputSource(telnetISR);
10494     }
10495     if (icsPR != NoProc) {
10496       DestroyChildProcess(icsPR, TRUE);
10497     }
10498
10499     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10500     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10501
10502     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10503     /* make sure this other one finishes before killing it!                  */
10504     if(endingGame) { int count = 0;
10505         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10506         while(endingGame && count++ < 10) DoSleep(1);
10507         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10508     }
10509
10510     /* Kill off chess programs */
10511     if (first.pr != NoProc) {
10512         ExitAnalyzeMode();
10513         
10514         DoSleep( appData.delayBeforeQuit );
10515         SendToProgram("quit\n", &first);
10516         DoSleep( appData.delayAfterQuit );
10517         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10518     }
10519     if (second.pr != NoProc) {
10520         DoSleep( appData.delayBeforeQuit );
10521         SendToProgram("quit\n", &second);
10522         DoSleep( appData.delayAfterQuit );
10523         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10524     }
10525     if (first.isr != NULL) {
10526         RemoveInputSource(first.isr);
10527     }
10528     if (second.isr != NULL) {
10529         RemoveInputSource(second.isr);
10530     }
10531
10532     ShutDownFrontEnd();
10533     exit(status);
10534 }
10535
10536 void
10537 PauseEvent()
10538 {
10539     if (appData.debugMode)
10540         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10541     if (pausing) {
10542         pausing = FALSE;
10543         ModeHighlight();
10544         if (gameMode == MachinePlaysWhite ||
10545             gameMode == MachinePlaysBlack) {
10546             StartClocks();
10547         } else {
10548             DisplayBothClocks();
10549         }
10550         if (gameMode == PlayFromGameFile) {
10551             if (appData.timeDelay >= 0) 
10552                 AutoPlayGameLoop();
10553         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10554             Reset(FALSE, TRUE);
10555             SendToICS(ics_prefix);
10556             SendToICS("refresh\n");
10557         } else if (currentMove < forwardMostMove) {
10558             ForwardInner(forwardMostMove);
10559         }
10560         pauseExamInvalid = FALSE;
10561     } else {
10562         switch (gameMode) {
10563           default:
10564             return;
10565           case IcsExamining:
10566             pauseExamForwardMostMove = forwardMostMove;
10567             pauseExamInvalid = FALSE;
10568             /* fall through */
10569           case IcsObserving:
10570           case IcsPlayingWhite:
10571           case IcsPlayingBlack:
10572             pausing = TRUE;
10573             ModeHighlight();
10574             return;
10575           case PlayFromGameFile:
10576             (void) StopLoadGameTimer();
10577             pausing = TRUE;
10578             ModeHighlight();
10579             break;
10580           case BeginningOfGame:
10581             if (appData.icsActive) return;
10582             /* else fall through */
10583           case MachinePlaysWhite:
10584           case MachinePlaysBlack:
10585           case TwoMachinesPlay:
10586             if (forwardMostMove == 0)
10587               return;           /* don't pause if no one has moved */
10588             if ((gameMode == MachinePlaysWhite &&
10589                  !WhiteOnMove(forwardMostMove)) ||
10590                 (gameMode == MachinePlaysBlack &&
10591                  WhiteOnMove(forwardMostMove))) {
10592                 StopClocks();
10593             }
10594             pausing = TRUE;
10595             ModeHighlight();
10596             break;
10597         }
10598     }
10599 }
10600
10601 void
10602 EditCommentEvent()
10603 {
10604     char title[MSG_SIZ];
10605
10606     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10607         strcpy(title, _("Edit comment"));
10608     } else {
10609         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10610                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10611                 parseList[currentMove - 1]);
10612     }
10613
10614     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10615 }
10616
10617
10618 void
10619 EditTagsEvent()
10620 {
10621     char *tags = PGNTags(&gameInfo);
10622     EditTagsPopUp(tags);
10623     free(tags);
10624 }
10625
10626 void
10627 AnalyzeModeEvent()
10628 {
10629     if (appData.noChessProgram || gameMode == AnalyzeMode)
10630       return;
10631
10632     if (gameMode != AnalyzeFile) {
10633         if (!appData.icsEngineAnalyze) {
10634                EditGameEvent();
10635                if (gameMode != EditGame) return;
10636         }
10637         ResurrectChessProgram();
10638         SendToProgram("analyze\n", &first);
10639         first.analyzing = TRUE;
10640         /*first.maybeThinking = TRUE;*/
10641         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10642         EngineOutputPopUp();
10643     }
10644     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10645     pausing = FALSE;
10646     ModeHighlight();
10647     SetGameInfo();
10648
10649     StartAnalysisClock();
10650     GetTimeMark(&lastNodeCountTime);
10651     lastNodeCount = 0;
10652 }
10653
10654 void
10655 AnalyzeFileEvent()
10656 {
10657     if (appData.noChessProgram || gameMode == AnalyzeFile)
10658       return;
10659
10660     if (gameMode != AnalyzeMode) {
10661         EditGameEvent();
10662         if (gameMode != EditGame) return;
10663         ResurrectChessProgram();
10664         SendToProgram("analyze\n", &first);
10665         first.analyzing = TRUE;
10666         /*first.maybeThinking = TRUE;*/
10667         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10668         EngineOutputPopUp();
10669     }
10670     gameMode = AnalyzeFile;
10671     pausing = FALSE;
10672     ModeHighlight();
10673     SetGameInfo();
10674
10675     StartAnalysisClock();
10676     GetTimeMark(&lastNodeCountTime);
10677     lastNodeCount = 0;
10678 }
10679
10680 void
10681 MachineWhiteEvent()
10682 {
10683     char buf[MSG_SIZ];
10684     char *bookHit = NULL;
10685
10686     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10687       return;
10688
10689
10690     if (gameMode == PlayFromGameFile || 
10691         gameMode == TwoMachinesPlay  || 
10692         gameMode == Training         || 
10693         gameMode == AnalyzeMode      || 
10694         gameMode == EndOfGame)
10695         EditGameEvent();
10696
10697     if (gameMode == EditPosition) 
10698         EditPositionDone();
10699
10700     if (!WhiteOnMove(currentMove)) {
10701         DisplayError(_("It is not White's turn"), 0);
10702         return;
10703     }
10704   
10705     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10706       ExitAnalyzeMode();
10707
10708     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10709         gameMode == AnalyzeFile)
10710         TruncateGame();
10711
10712     ResurrectChessProgram();    /* in case it isn't running */
10713     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10714         gameMode = MachinePlaysWhite;
10715         ResetClocks();
10716     } else
10717     gameMode = MachinePlaysWhite;
10718     pausing = FALSE;
10719     ModeHighlight();
10720     SetGameInfo();
10721     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10722     DisplayTitle(buf);
10723     if (first.sendName) {
10724       sprintf(buf, "name %s\n", gameInfo.black);
10725       SendToProgram(buf, &first);
10726     }
10727     if (first.sendTime) {
10728       if (first.useColors) {
10729         SendToProgram("black\n", &first); /*gnu kludge*/
10730       }
10731       SendTimeRemaining(&first, TRUE);
10732     }
10733     if (first.useColors) {
10734       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10735     }
10736     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10737     SetMachineThinkingEnables();
10738     first.maybeThinking = TRUE;
10739     StartClocks();
10740     firstMove = FALSE;
10741
10742     if (appData.autoFlipView && !flipView) {
10743       flipView = !flipView;
10744       DrawPosition(FALSE, NULL);
10745       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10746     }
10747
10748     if(bookHit) { // [HGM] book: simulate book reply
10749         static char bookMove[MSG_SIZ]; // a bit generous?
10750
10751         programStats.nodes = programStats.depth = programStats.time = 
10752         programStats.score = programStats.got_only_move = 0;
10753         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10754
10755         strcpy(bookMove, "move ");
10756         strcat(bookMove, bookHit);
10757         HandleMachineMove(bookMove, &first);
10758     }
10759 }
10760
10761 void
10762 MachineBlackEvent()
10763 {
10764     char buf[MSG_SIZ];
10765    char *bookHit = NULL;
10766
10767     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10768         return;
10769
10770
10771     if (gameMode == PlayFromGameFile || 
10772         gameMode == TwoMachinesPlay  || 
10773         gameMode == Training         || 
10774         gameMode == AnalyzeMode      || 
10775         gameMode == EndOfGame)
10776         EditGameEvent();
10777
10778     if (gameMode == EditPosition) 
10779         EditPositionDone();
10780
10781     if (WhiteOnMove(currentMove)) {
10782         DisplayError(_("It is not Black's turn"), 0);
10783         return;
10784     }
10785     
10786     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10787       ExitAnalyzeMode();
10788
10789     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10790         gameMode == AnalyzeFile)
10791         TruncateGame();
10792
10793     ResurrectChessProgram();    /* in case it isn't running */
10794     gameMode = MachinePlaysBlack;
10795     pausing = FALSE;
10796     ModeHighlight();
10797     SetGameInfo();
10798     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10799     DisplayTitle(buf);
10800     if (first.sendName) {
10801       sprintf(buf, "name %s\n", gameInfo.white);
10802       SendToProgram(buf, &first);
10803     }
10804     if (first.sendTime) {
10805       if (first.useColors) {
10806         SendToProgram("white\n", &first); /*gnu kludge*/
10807       }
10808       SendTimeRemaining(&first, FALSE);
10809     }
10810     if (first.useColors) {
10811       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10812     }
10813     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10814     SetMachineThinkingEnables();
10815     first.maybeThinking = TRUE;
10816     StartClocks();
10817
10818     if (appData.autoFlipView && flipView) {
10819       flipView = !flipView;
10820       DrawPosition(FALSE, NULL);
10821       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10822     }
10823     if(bookHit) { // [HGM] book: simulate book reply
10824         static char bookMove[MSG_SIZ]; // a bit generous?
10825
10826         programStats.nodes = programStats.depth = programStats.time = 
10827         programStats.score = programStats.got_only_move = 0;
10828         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10829
10830         strcpy(bookMove, "move ");
10831         strcat(bookMove, bookHit);
10832         HandleMachineMove(bookMove, &first);
10833     }
10834 }
10835
10836
10837 void
10838 DisplayTwoMachinesTitle()
10839 {
10840     char buf[MSG_SIZ];
10841     if (appData.matchGames > 0) {
10842         if (first.twoMachinesColor[0] == 'w') {
10843             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10844                     gameInfo.white, gameInfo.black,
10845                     first.matchWins, second.matchWins,
10846                     matchGame - 1 - (first.matchWins + second.matchWins));
10847         } else {
10848             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10849                     gameInfo.white, gameInfo.black,
10850                     second.matchWins, first.matchWins,
10851                     matchGame - 1 - (first.matchWins + second.matchWins));
10852         }
10853     } else {
10854         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10855     }
10856     DisplayTitle(buf);
10857 }
10858
10859 void
10860 TwoMachinesEvent P((void))
10861 {
10862     int i;
10863     char buf[MSG_SIZ];
10864     ChessProgramState *onmove;
10865     char *bookHit = NULL;
10866     
10867     if (appData.noChessProgram) return;
10868
10869     switch (gameMode) {
10870       case TwoMachinesPlay:
10871         return;
10872       case MachinePlaysWhite:
10873       case MachinePlaysBlack:
10874         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10875             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10876             return;
10877         }
10878         /* fall through */
10879       case BeginningOfGame:
10880       case PlayFromGameFile:
10881       case EndOfGame:
10882         EditGameEvent();
10883         if (gameMode != EditGame) return;
10884         break;
10885       case EditPosition:
10886         EditPositionDone();
10887         break;
10888       case AnalyzeMode:
10889       case AnalyzeFile:
10890         ExitAnalyzeMode();
10891         break;
10892       case EditGame:
10893       default:
10894         break;
10895     }
10896
10897     forwardMostMove = currentMove;
10898     ResurrectChessProgram();    /* in case first program isn't running */
10899
10900     if (second.pr == NULL) {
10901         StartChessProgram(&second);
10902         if (second.protocolVersion == 1) {
10903           TwoMachinesEventIfReady();
10904         } else {
10905           /* kludge: allow timeout for initial "feature" command */
10906           FreezeUI();
10907           DisplayMessage("", _("Starting second chess program"));
10908           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10909         }
10910         return;
10911     }
10912     DisplayMessage("", "");
10913     InitChessProgram(&second, FALSE);
10914     SendToProgram("force\n", &second);
10915     if (startedFromSetupPosition) {
10916         SendBoard(&second, backwardMostMove);
10917     if (appData.debugMode) {
10918         fprintf(debugFP, "Two Machines\n");
10919     }
10920     }
10921     for (i = backwardMostMove; i < forwardMostMove; i++) {
10922         SendMoveToProgram(i, &second);
10923     }
10924
10925     gameMode = TwoMachinesPlay;
10926     pausing = FALSE;
10927     ModeHighlight();
10928     SetGameInfo();
10929     DisplayTwoMachinesTitle();
10930     firstMove = TRUE;
10931     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10932         onmove = &first;
10933     } else {
10934         onmove = &second;
10935     }
10936
10937     SendToProgram(first.computerString, &first);
10938     if (first.sendName) {
10939       sprintf(buf, "name %s\n", second.tidy);
10940       SendToProgram(buf, &first);
10941     }
10942     SendToProgram(second.computerString, &second);
10943     if (second.sendName) {
10944       sprintf(buf, "name %s\n", first.tidy);
10945       SendToProgram(buf, &second);
10946     }
10947
10948     ResetClocks();
10949     if (!first.sendTime || !second.sendTime) {
10950         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10951         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10952     }
10953     if (onmove->sendTime) {
10954       if (onmove->useColors) {
10955         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10956       }
10957       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10958     }
10959     if (onmove->useColors) {
10960       SendToProgram(onmove->twoMachinesColor, onmove);
10961     }
10962     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10963 //    SendToProgram("go\n", onmove);
10964     onmove->maybeThinking = TRUE;
10965     SetMachineThinkingEnables();
10966
10967     StartClocks();
10968
10969     if(bookHit) { // [HGM] book: simulate book reply
10970         static char bookMove[MSG_SIZ]; // a bit generous?
10971
10972         programStats.nodes = programStats.depth = programStats.time = 
10973         programStats.score = programStats.got_only_move = 0;
10974         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10975
10976         strcpy(bookMove, "move ");
10977         strcat(bookMove, bookHit);
10978         savedMessage = bookMove; // args for deferred call
10979         savedState = onmove;
10980         ScheduleDelayedEvent(DeferredBookMove, 1);
10981     }
10982 }
10983
10984 void
10985 TrainingEvent()
10986 {
10987     if (gameMode == Training) {
10988       SetTrainingModeOff();
10989       gameMode = PlayFromGameFile;
10990       DisplayMessage("", _("Training mode off"));
10991     } else {
10992       gameMode = Training;
10993       animateTraining = appData.animate;
10994
10995       /* make sure we are not already at the end of the game */
10996       if (currentMove < forwardMostMove) {
10997         SetTrainingModeOn();
10998         DisplayMessage("", _("Training mode on"));
10999       } else {
11000         gameMode = PlayFromGameFile;
11001         DisplayError(_("Already at end of game"), 0);
11002       }
11003     }
11004     ModeHighlight();
11005 }
11006
11007 void
11008 IcsClientEvent()
11009 {
11010     if (!appData.icsActive) return;
11011     switch (gameMode) {
11012       case IcsPlayingWhite:
11013       case IcsPlayingBlack:
11014       case IcsObserving:
11015       case IcsIdle:
11016       case BeginningOfGame:
11017       case IcsExamining:
11018         return;
11019
11020       case EditGame:
11021         break;
11022
11023       case EditPosition:
11024         EditPositionDone();
11025         break;
11026
11027       case AnalyzeMode:
11028       case AnalyzeFile:
11029         ExitAnalyzeMode();
11030         break;
11031         
11032       default:
11033         EditGameEvent();
11034         break;
11035     }
11036
11037     gameMode = IcsIdle;
11038     ModeHighlight();
11039     return;
11040 }
11041
11042
11043 void
11044 EditGameEvent()
11045 {
11046     int i;
11047
11048     switch (gameMode) {
11049       case Training:
11050         SetTrainingModeOff();
11051         break;
11052       case MachinePlaysWhite:
11053       case MachinePlaysBlack:
11054       case BeginningOfGame:
11055         SendToProgram("force\n", &first);
11056         SetUserThinkingEnables();
11057         break;
11058       case PlayFromGameFile:
11059         (void) StopLoadGameTimer();
11060         if (gameFileFP != NULL) {
11061             gameFileFP = NULL;
11062         }
11063         break;
11064       case EditPosition:
11065         EditPositionDone();
11066         break;
11067       case AnalyzeMode:
11068       case AnalyzeFile:
11069         ExitAnalyzeMode();
11070         SendToProgram("force\n", &first);
11071         break;
11072       case TwoMachinesPlay:
11073         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11074         ResurrectChessProgram();
11075         SetUserThinkingEnables();
11076         break;
11077       case EndOfGame:
11078         ResurrectChessProgram();
11079         break;
11080       case IcsPlayingBlack:
11081       case IcsPlayingWhite:
11082         DisplayError(_("Warning: You are still playing a game"), 0);
11083         break;
11084       case IcsObserving:
11085         DisplayError(_("Warning: You are still observing a game"), 0);
11086         break;
11087       case IcsExamining:
11088         DisplayError(_("Warning: You are still examining a game"), 0);
11089         break;
11090       case IcsIdle:
11091         break;
11092       case EditGame:
11093       default:
11094         return;
11095     }
11096     
11097     pausing = FALSE;
11098     StopClocks();
11099     first.offeredDraw = second.offeredDraw = 0;
11100
11101     if (gameMode == PlayFromGameFile) {
11102         whiteTimeRemaining = timeRemaining[0][currentMove];
11103         blackTimeRemaining = timeRemaining[1][currentMove];
11104         DisplayTitle("");
11105     }
11106
11107     if (gameMode == MachinePlaysWhite ||
11108         gameMode == MachinePlaysBlack ||
11109         gameMode == TwoMachinesPlay ||
11110         gameMode == EndOfGame) {
11111         i = forwardMostMove;
11112         while (i > currentMove) {
11113             SendToProgram("undo\n", &first);
11114             i--;
11115         }
11116         whiteTimeRemaining = timeRemaining[0][currentMove];
11117         blackTimeRemaining = timeRemaining[1][currentMove];
11118         DisplayBothClocks();
11119         if (whiteFlag || blackFlag) {
11120             whiteFlag = blackFlag = 0;
11121         }
11122         DisplayTitle("");
11123     }           
11124     
11125     gameMode = EditGame;
11126     ModeHighlight();
11127     SetGameInfo();
11128 }
11129
11130
11131 void
11132 EditPositionEvent()
11133 {
11134     if (gameMode == EditPosition) {
11135         EditGameEvent();
11136         return;
11137     }
11138     
11139     EditGameEvent();
11140     if (gameMode != EditGame) return;
11141     
11142     gameMode = EditPosition;
11143     ModeHighlight();
11144     SetGameInfo();
11145     if (currentMove > 0)
11146       CopyBoard(boards[0], boards[currentMove]);
11147     
11148     blackPlaysFirst = !WhiteOnMove(currentMove);
11149     ResetClocks();
11150     currentMove = forwardMostMove = backwardMostMove = 0;
11151     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11152     DisplayMove(-1);
11153 }
11154
11155 void
11156 ExitAnalyzeMode()
11157 {
11158     /* [DM] icsEngineAnalyze - possible call from other functions */
11159     if (appData.icsEngineAnalyze) {
11160         appData.icsEngineAnalyze = FALSE;
11161
11162         DisplayMessage("",_("Close ICS engine analyze..."));
11163     }
11164     if (first.analysisSupport && first.analyzing) {
11165       SendToProgram("exit\n", &first);
11166       first.analyzing = FALSE;
11167     }
11168     thinkOutput[0] = NULLCHAR;
11169 }
11170
11171 void
11172 EditPositionDone()
11173 {
11174     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11175
11176     startedFromSetupPosition = TRUE;
11177     InitChessProgram(&first, FALSE);
11178     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11179     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11180         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11181         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11182     } else castlingRights[0][2] = -1;
11183     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11184         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11185         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11186     } else castlingRights[0][5] = -1;
11187     SendToProgram("force\n", &first);
11188     if (blackPlaysFirst) {
11189         strcpy(moveList[0], "");
11190         strcpy(parseList[0], "");
11191         currentMove = forwardMostMove = backwardMostMove = 1;
11192         CopyBoard(boards[1], boards[0]);
11193         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11194         { int i;
11195           epStatus[1] = epStatus[0];
11196           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11197         }
11198     } else {
11199         currentMove = forwardMostMove = backwardMostMove = 0;
11200     }
11201     SendBoard(&first, forwardMostMove);
11202     if (appData.debugMode) {
11203         fprintf(debugFP, "EditPosDone\n");
11204     }
11205     DisplayTitle("");
11206     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11207     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11208     gameMode = EditGame;
11209     ModeHighlight();
11210     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11211     ClearHighlights(); /* [AS] */
11212 }
11213
11214 /* Pause for `ms' milliseconds */
11215 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11216 void
11217 TimeDelay(ms)
11218      long ms;
11219 {
11220     TimeMark m1, m2;
11221
11222     GetTimeMark(&m1);
11223     do {
11224         GetTimeMark(&m2);
11225     } while (SubtractTimeMarks(&m2, &m1) < ms);
11226 }
11227
11228 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11229 void
11230 SendMultiLineToICS(buf)
11231      char *buf;
11232 {
11233     char temp[MSG_SIZ+1], *p;
11234     int len;
11235
11236     len = strlen(buf);
11237     if (len > MSG_SIZ)
11238       len = MSG_SIZ;
11239   
11240     strncpy(temp, buf, len);
11241     temp[len] = 0;
11242
11243     p = temp;
11244     while (*p) {
11245         if (*p == '\n' || *p == '\r')
11246           *p = ' ';
11247         ++p;
11248     }
11249
11250     strcat(temp, "\n");
11251     SendToICS(temp);
11252     SendToPlayer(temp, strlen(temp));
11253 }
11254
11255 void
11256 SetWhiteToPlayEvent()
11257 {
11258     if (gameMode == EditPosition) {
11259         blackPlaysFirst = FALSE;
11260         DisplayBothClocks();    /* works because currentMove is 0 */
11261     } else if (gameMode == IcsExamining) {
11262         SendToICS(ics_prefix);
11263         SendToICS("tomove white\n");
11264     }
11265 }
11266
11267 void
11268 SetBlackToPlayEvent()
11269 {
11270     if (gameMode == EditPosition) {
11271         blackPlaysFirst = TRUE;
11272         currentMove = 1;        /* kludge */
11273         DisplayBothClocks();
11274         currentMove = 0;
11275     } else if (gameMode == IcsExamining) {
11276         SendToICS(ics_prefix);
11277         SendToICS("tomove black\n");
11278     }
11279 }
11280
11281 void
11282 EditPositionMenuEvent(selection, x, y)
11283      ChessSquare selection;
11284      int x, y;
11285 {
11286     char buf[MSG_SIZ];
11287     ChessSquare piece = boards[0][y][x];
11288
11289     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11290
11291     switch (selection) {
11292       case ClearBoard:
11293         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11294             SendToICS(ics_prefix);
11295             SendToICS("bsetup clear\n");
11296         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11297             SendToICS(ics_prefix);
11298             SendToICS("clearboard\n");
11299         } else {
11300             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11301                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11302                 for (y = 0; y < BOARD_HEIGHT; y++) {
11303                     if (gameMode == IcsExamining) {
11304                         if (boards[currentMove][y][x] != EmptySquare) {
11305                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11306                                     AAA + x, ONE + y);
11307                             SendToICS(buf);
11308                         }
11309                     } else {
11310                         boards[0][y][x] = p;
11311                     }
11312                 }
11313             }
11314         }
11315         if (gameMode == EditPosition) {
11316             DrawPosition(FALSE, boards[0]);
11317         }
11318         break;
11319
11320       case WhitePlay:
11321         SetWhiteToPlayEvent();
11322         break;
11323
11324       case BlackPlay:
11325         SetBlackToPlayEvent();
11326         break;
11327
11328       case EmptySquare:
11329         if (gameMode == IcsExamining) {
11330             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11331             SendToICS(buf);
11332         } else {
11333             boards[0][y][x] = EmptySquare;
11334             DrawPosition(FALSE, boards[0]);
11335         }
11336         break;
11337
11338       case PromotePiece:
11339         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11340            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11341             selection = (ChessSquare) (PROMOTED piece);
11342         } else if(piece == EmptySquare) selection = WhiteSilver;
11343         else selection = (ChessSquare)((int)piece - 1);
11344         goto defaultlabel;
11345
11346       case DemotePiece:
11347         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11348            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11349             selection = (ChessSquare) (DEMOTED piece);
11350         } else if(piece == EmptySquare) selection = BlackSilver;
11351         else selection = (ChessSquare)((int)piece + 1);       
11352         goto defaultlabel;
11353
11354       case WhiteQueen:
11355       case BlackQueen:
11356         if(gameInfo.variant == VariantShatranj ||
11357            gameInfo.variant == VariantXiangqi  ||
11358            gameInfo.variant == VariantCourier    )
11359             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11360         goto defaultlabel;
11361
11362       case WhiteKing:
11363       case BlackKing:
11364         if(gameInfo.variant == VariantXiangqi)
11365             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11366         if(gameInfo.variant == VariantKnightmate)
11367             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11368       default:
11369         defaultlabel:
11370         if (gameMode == IcsExamining) {
11371             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11372                     PieceToChar(selection), AAA + x, ONE + y);
11373             SendToICS(buf);
11374         } else {
11375             boards[0][y][x] = selection;
11376             DrawPosition(FALSE, boards[0]);
11377         }
11378         break;
11379     }
11380 }
11381
11382
11383 void
11384 DropMenuEvent(selection, x, y)
11385      ChessSquare selection;
11386      int x, y;
11387 {
11388     ChessMove moveType;
11389
11390     switch (gameMode) {
11391       case IcsPlayingWhite:
11392       case MachinePlaysBlack:
11393         if (!WhiteOnMove(currentMove)) {
11394             DisplayMoveError(_("It is Black's turn"));
11395             return;
11396         }
11397         moveType = WhiteDrop;
11398         break;
11399       case IcsPlayingBlack:
11400       case MachinePlaysWhite:
11401         if (WhiteOnMove(currentMove)) {
11402             DisplayMoveError(_("It is White's turn"));
11403             return;
11404         }
11405         moveType = BlackDrop;
11406         break;
11407       case EditGame:
11408         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11409         break;
11410       default:
11411         return;
11412     }
11413
11414     if (moveType == BlackDrop && selection < BlackPawn) {
11415       selection = (ChessSquare) ((int) selection
11416                                  + (int) BlackPawn - (int) WhitePawn);
11417     }
11418     if (boards[currentMove][y][x] != EmptySquare) {
11419         DisplayMoveError(_("That square is occupied"));
11420         return;
11421     }
11422
11423     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11424 }
11425
11426 void
11427 AcceptEvent()
11428 {
11429     /* Accept a pending offer of any kind from opponent */
11430     
11431     if (appData.icsActive) {
11432         SendToICS(ics_prefix);
11433         SendToICS("accept\n");
11434     } else if (cmailMsgLoaded) {
11435         if (currentMove == cmailOldMove &&
11436             commentList[cmailOldMove] != NULL &&
11437             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11438                    "Black offers a draw" : "White offers a draw")) {
11439             TruncateGame();
11440             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11441             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11442         } else {
11443             DisplayError(_("There is no pending offer on this move"), 0);
11444             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11445         }
11446     } else {
11447         /* Not used for offers from chess program */
11448     }
11449 }
11450
11451 void
11452 DeclineEvent()
11453 {
11454     /* Decline a pending offer of any kind from opponent */
11455     
11456     if (appData.icsActive) {
11457         SendToICS(ics_prefix);
11458         SendToICS("decline\n");
11459     } else if (cmailMsgLoaded) {
11460         if (currentMove == cmailOldMove &&
11461             commentList[cmailOldMove] != NULL &&
11462             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11463                    "Black offers a draw" : "White offers a draw")) {
11464 #ifdef NOTDEF
11465             AppendComment(cmailOldMove, "Draw declined");
11466             DisplayComment(cmailOldMove - 1, "Draw declined");
11467 #endif /*NOTDEF*/
11468         } else {
11469             DisplayError(_("There is no pending offer on this move"), 0);
11470         }
11471     } else {
11472         /* Not used for offers from chess program */
11473     }
11474 }
11475
11476 void
11477 RematchEvent()
11478 {
11479     /* Issue ICS rematch command */
11480     if (appData.icsActive) {
11481         SendToICS(ics_prefix);
11482         SendToICS("rematch\n");
11483     }
11484 }
11485
11486 void
11487 CallFlagEvent()
11488 {
11489     /* Call your opponent's flag (claim a win on time) */
11490     if (appData.icsActive) {
11491         SendToICS(ics_prefix);
11492         SendToICS("flag\n");
11493     } else {
11494         switch (gameMode) {
11495           default:
11496             return;
11497           case MachinePlaysWhite:
11498             if (whiteFlag) {
11499                 if (blackFlag)
11500                   GameEnds(GameIsDrawn, "Both players ran out of time",
11501                            GE_PLAYER);
11502                 else
11503                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11504             } else {
11505                 DisplayError(_("Your opponent is not out of time"), 0);
11506             }
11507             break;
11508           case MachinePlaysBlack:
11509             if (blackFlag) {
11510                 if (whiteFlag)
11511                   GameEnds(GameIsDrawn, "Both players ran out of time",
11512                            GE_PLAYER);
11513                 else
11514                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11515             } else {
11516                 DisplayError(_("Your opponent is not out of time"), 0);
11517             }
11518             break;
11519         }
11520     }
11521 }
11522
11523 void
11524 DrawEvent()
11525 {
11526     /* Offer draw or accept pending draw offer from opponent */
11527     
11528     if (appData.icsActive) {
11529         /* Note: tournament rules require draw offers to be
11530            made after you make your move but before you punch
11531            your clock.  Currently ICS doesn't let you do that;
11532            instead, you immediately punch your clock after making
11533            a move, but you can offer a draw at any time. */
11534         
11535         SendToICS(ics_prefix);
11536         SendToICS("draw\n");
11537     } else if (cmailMsgLoaded) {
11538         if (currentMove == cmailOldMove &&
11539             commentList[cmailOldMove] != NULL &&
11540             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11541                    "Black offers a draw" : "White offers a draw")) {
11542             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11543             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11544         } else if (currentMove == cmailOldMove + 1) {
11545             char *offer = WhiteOnMove(cmailOldMove) ?
11546               "White offers a draw" : "Black offers a draw";
11547             AppendComment(currentMove, offer);
11548             DisplayComment(currentMove - 1, offer);
11549             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11550         } else {
11551             DisplayError(_("You must make your move before offering a draw"), 0);
11552             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11553         }
11554     } else if (first.offeredDraw) {
11555         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11556     } else {
11557         if (first.sendDrawOffers) {
11558             SendToProgram("draw\n", &first);
11559             userOfferedDraw = TRUE;
11560         }
11561     }
11562 }
11563
11564 void
11565 AdjournEvent()
11566 {
11567     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11568     
11569     if (appData.icsActive) {
11570         SendToICS(ics_prefix);
11571         SendToICS("adjourn\n");
11572     } else {
11573         /* Currently GNU Chess doesn't offer or accept Adjourns */
11574     }
11575 }
11576
11577
11578 void
11579 AbortEvent()
11580 {
11581     /* Offer Abort or accept pending Abort offer from opponent */
11582     
11583     if (appData.icsActive) {
11584         SendToICS(ics_prefix);
11585         SendToICS("abort\n");
11586     } else {
11587         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11588     }
11589 }
11590
11591 void
11592 ResignEvent()
11593 {
11594     /* Resign.  You can do this even if it's not your turn. */
11595     
11596     if (appData.icsActive) {
11597         SendToICS(ics_prefix);
11598         SendToICS("resign\n");
11599     } else {
11600         switch (gameMode) {
11601           case MachinePlaysWhite:
11602             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11603             break;
11604           case MachinePlaysBlack:
11605             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11606             break;
11607           case EditGame:
11608             if (cmailMsgLoaded) {
11609                 TruncateGame();
11610                 if (WhiteOnMove(cmailOldMove)) {
11611                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11612                 } else {
11613                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11614                 }
11615                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11616             }
11617             break;
11618           default:
11619             break;
11620         }
11621     }
11622 }
11623
11624
11625 void
11626 StopObservingEvent()
11627 {
11628     /* Stop observing current games */
11629     SendToICS(ics_prefix);
11630     SendToICS("unobserve\n");
11631 }
11632
11633 void
11634 StopExaminingEvent()
11635 {
11636     /* Stop observing current game */
11637     SendToICS(ics_prefix);
11638     SendToICS("unexamine\n");
11639 }
11640
11641 void
11642 ForwardInner(target)
11643      int target;
11644 {
11645     int limit;
11646
11647     if (appData.debugMode)
11648         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11649                 target, currentMove, forwardMostMove);
11650
11651     if (gameMode == EditPosition)
11652       return;
11653
11654     if (gameMode == PlayFromGameFile && !pausing)
11655       PauseEvent();
11656     
11657     if (gameMode == IcsExamining && pausing)
11658       limit = pauseExamForwardMostMove;
11659     else
11660       limit = forwardMostMove;
11661     
11662     if (target > limit) target = limit;
11663
11664     if (target > 0 && moveList[target - 1][0]) {
11665         int fromX, fromY, toX, toY;
11666         toX = moveList[target - 1][2] - AAA;
11667         toY = moveList[target - 1][3] - ONE;
11668         if (moveList[target - 1][1] == '@') {
11669             if (appData.highlightLastMove) {
11670                 SetHighlights(-1, -1, toX, toY);
11671             }
11672         } else {
11673             fromX = moveList[target - 1][0] - AAA;
11674             fromY = moveList[target - 1][1] - ONE;
11675             if (target == currentMove + 1) {
11676                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11677             }
11678             if (appData.highlightLastMove) {
11679                 SetHighlights(fromX, fromY, toX, toY);
11680             }
11681         }
11682     }
11683     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11684         gameMode == Training || gameMode == PlayFromGameFile || 
11685         gameMode == AnalyzeFile) {
11686         while (currentMove < target) {
11687             SendMoveToProgram(currentMove++, &first);
11688         }
11689     } else {
11690         currentMove = target;
11691     }
11692     
11693     if (gameMode == EditGame || gameMode == EndOfGame) {
11694         whiteTimeRemaining = timeRemaining[0][currentMove];
11695         blackTimeRemaining = timeRemaining[1][currentMove];
11696     }
11697     DisplayBothClocks();
11698     DisplayMove(currentMove - 1);
11699     DrawPosition(FALSE, boards[currentMove]);
11700     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11701     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11702         DisplayComment(currentMove - 1, commentList[currentMove]);
11703     }
11704 }
11705
11706
11707 void
11708 ForwardEvent()
11709 {
11710     if (gameMode == IcsExamining && !pausing) {
11711         SendToICS(ics_prefix);
11712         SendToICS("forward\n");
11713     } else {
11714         ForwardInner(currentMove + 1);
11715     }
11716 }
11717
11718 void
11719 ToEndEvent()
11720 {
11721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11722         /* to optimze, we temporarily turn off analysis mode while we feed
11723          * the remaining moves to the engine. Otherwise we get analysis output
11724          * after each move.
11725          */ 
11726         if (first.analysisSupport) {
11727           SendToProgram("exit\nforce\n", &first);
11728           first.analyzing = FALSE;
11729         }
11730     }
11731         
11732     if (gameMode == IcsExamining && !pausing) {
11733         SendToICS(ics_prefix);
11734         SendToICS("forward 999999\n");
11735     } else {
11736         ForwardInner(forwardMostMove);
11737     }
11738
11739     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11740         /* we have fed all the moves, so reactivate analysis mode */
11741         SendToProgram("analyze\n", &first);
11742         first.analyzing = TRUE;
11743         /*first.maybeThinking = TRUE;*/
11744         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11745     }
11746 }
11747
11748 void
11749 BackwardInner(target)
11750      int target;
11751 {
11752     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11753
11754     if (appData.debugMode)
11755         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11756                 target, currentMove, forwardMostMove);
11757
11758     if (gameMode == EditPosition) return;
11759     if (currentMove <= backwardMostMove) {
11760         ClearHighlights();
11761         DrawPosition(full_redraw, boards[currentMove]);
11762         return;
11763     }
11764     if (gameMode == PlayFromGameFile && !pausing)
11765       PauseEvent();
11766     
11767     if (moveList[target][0]) {
11768         int fromX, fromY, toX, toY;
11769         toX = moveList[target][2] - AAA;
11770         toY = moveList[target][3] - ONE;
11771         if (moveList[target][1] == '@') {
11772             if (appData.highlightLastMove) {
11773                 SetHighlights(-1, -1, toX, toY);
11774             }
11775         } else {
11776             fromX = moveList[target][0] - AAA;
11777             fromY = moveList[target][1] - ONE;
11778             if (target == currentMove - 1) {
11779                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11780             }
11781             if (appData.highlightLastMove) {
11782                 SetHighlights(fromX, fromY, toX, toY);
11783             }
11784         }
11785     }
11786     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11787         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11788         while (currentMove > target) {
11789             SendToProgram("undo\n", &first);
11790             currentMove--;
11791         }
11792     } else {
11793         currentMove = target;
11794     }
11795     
11796     if (gameMode == EditGame || gameMode == EndOfGame) {
11797         whiteTimeRemaining = timeRemaining[0][currentMove];
11798         blackTimeRemaining = timeRemaining[1][currentMove];
11799     }
11800     DisplayBothClocks();
11801     DisplayMove(currentMove - 1);
11802     DrawPosition(full_redraw, boards[currentMove]);
11803     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11804     // [HGM] PV info: routine tests if comment empty
11805     DisplayComment(currentMove - 1, commentList[currentMove]);
11806 }
11807
11808 void
11809 BackwardEvent()
11810 {
11811     if (gameMode == IcsExamining && !pausing) {
11812         SendToICS(ics_prefix);
11813         SendToICS("backward\n");
11814     } else {
11815         BackwardInner(currentMove - 1);
11816     }
11817 }
11818
11819 void
11820 ToStartEvent()
11821 {
11822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11823         /* to optimze, we temporarily turn off analysis mode while we undo
11824          * all the moves. Otherwise we get analysis output after each undo.
11825          */ 
11826         if (first.analysisSupport) {
11827           SendToProgram("exit\nforce\n", &first);
11828           first.analyzing = FALSE;
11829         }
11830     }
11831
11832     if (gameMode == IcsExamining && !pausing) {
11833         SendToICS(ics_prefix);
11834         SendToICS("backward 999999\n");
11835     } else {
11836         BackwardInner(backwardMostMove);
11837     }
11838
11839     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11840         /* we have fed all the moves, so reactivate analysis mode */
11841         SendToProgram("analyze\n", &first);
11842         first.analyzing = TRUE;
11843         /*first.maybeThinking = TRUE;*/
11844         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11845     }
11846 }
11847
11848 void
11849 ToNrEvent(int to)
11850 {
11851   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11852   if (to >= forwardMostMove) to = forwardMostMove;
11853   if (to <= backwardMostMove) to = backwardMostMove;
11854   if (to < currentMove) {
11855     BackwardInner(to);
11856   } else {
11857     ForwardInner(to);
11858   }
11859 }
11860
11861 void
11862 RevertEvent()
11863 {
11864     if (gameMode != IcsExamining) {
11865         DisplayError(_("You are not examining a game"), 0);
11866         return;
11867     }
11868     if (pausing) {
11869         DisplayError(_("You can't revert while pausing"), 0);
11870         return;
11871     }
11872     SendToICS(ics_prefix);
11873     SendToICS("revert\n");
11874 }
11875
11876 void
11877 RetractMoveEvent()
11878 {
11879     switch (gameMode) {
11880       case MachinePlaysWhite:
11881       case MachinePlaysBlack:
11882         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11883             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11884             return;
11885         }
11886         if (forwardMostMove < 2) return;
11887         currentMove = forwardMostMove = forwardMostMove - 2;
11888         whiteTimeRemaining = timeRemaining[0][currentMove];
11889         blackTimeRemaining = timeRemaining[1][currentMove];
11890         DisplayBothClocks();
11891         DisplayMove(currentMove - 1);
11892         ClearHighlights();/*!! could figure this out*/
11893         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11894         SendToProgram("remove\n", &first);
11895         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11896         break;
11897
11898       case BeginningOfGame:
11899       default:
11900         break;
11901
11902       case IcsPlayingWhite:
11903       case IcsPlayingBlack:
11904         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11905             SendToICS(ics_prefix);
11906             SendToICS("takeback 2\n");
11907         } else {
11908             SendToICS(ics_prefix);
11909             SendToICS("takeback 1\n");
11910         }
11911         break;
11912     }
11913 }
11914
11915 void
11916 MoveNowEvent()
11917 {
11918     ChessProgramState *cps;
11919
11920     switch (gameMode) {
11921       case MachinePlaysWhite:
11922         if (!WhiteOnMove(forwardMostMove)) {
11923             DisplayError(_("It is your turn"), 0);
11924             return;
11925         }
11926         cps = &first;
11927         break;
11928       case MachinePlaysBlack:
11929         if (WhiteOnMove(forwardMostMove)) {
11930             DisplayError(_("It is your turn"), 0);
11931             return;
11932         }
11933         cps = &first;
11934         break;
11935       case TwoMachinesPlay:
11936         if (WhiteOnMove(forwardMostMove) ==
11937             (first.twoMachinesColor[0] == 'w')) {
11938             cps = &first;
11939         } else {
11940             cps = &second;
11941         }
11942         break;
11943       case BeginningOfGame:
11944       default:
11945         return;
11946     }
11947     SendToProgram("?\n", cps);
11948 }
11949
11950 void
11951 TruncateGameEvent()
11952 {
11953     EditGameEvent();
11954     if (gameMode != EditGame) return;
11955     TruncateGame();
11956 }
11957
11958 void
11959 TruncateGame()
11960 {
11961     if (forwardMostMove > currentMove) {
11962         if (gameInfo.resultDetails != NULL) {
11963             free(gameInfo.resultDetails);
11964             gameInfo.resultDetails = NULL;
11965             gameInfo.result = GameUnfinished;
11966         }
11967         forwardMostMove = currentMove;
11968         HistorySet(parseList, backwardMostMove, forwardMostMove,
11969                    currentMove-1);
11970     }
11971 }
11972
11973 void
11974 HintEvent()
11975 {
11976     if (appData.noChessProgram) return;
11977     switch (gameMode) {
11978       case MachinePlaysWhite:
11979         if (WhiteOnMove(forwardMostMove)) {
11980             DisplayError(_("Wait until your turn"), 0);
11981             return;
11982         }
11983         break;
11984       case BeginningOfGame:
11985       case MachinePlaysBlack:
11986         if (!WhiteOnMove(forwardMostMove)) {
11987             DisplayError(_("Wait until your turn"), 0);
11988             return;
11989         }
11990         break;
11991       default:
11992         DisplayError(_("No hint available"), 0);
11993         return;
11994     }
11995     SendToProgram("hint\n", &first);
11996     hintRequested = TRUE;
11997 }
11998
11999 void
12000 BookEvent()
12001 {
12002     if (appData.noChessProgram) return;
12003     switch (gameMode) {
12004       case MachinePlaysWhite:
12005         if (WhiteOnMove(forwardMostMove)) {
12006             DisplayError(_("Wait until your turn"), 0);
12007             return;
12008         }
12009         break;
12010       case BeginningOfGame:
12011       case MachinePlaysBlack:
12012         if (!WhiteOnMove(forwardMostMove)) {
12013             DisplayError(_("Wait until your turn"), 0);
12014             return;
12015         }
12016         break;
12017       case EditPosition:
12018         EditPositionDone();
12019         break;
12020       case TwoMachinesPlay:
12021         return;
12022       default:
12023         break;
12024     }
12025     SendToProgram("bk\n", &first);
12026     bookOutput[0] = NULLCHAR;
12027     bookRequested = TRUE;
12028 }
12029
12030 void
12031 AboutGameEvent()
12032 {
12033     char *tags = PGNTags(&gameInfo);
12034     TagsPopUp(tags, CmailMsg());
12035     free(tags);
12036 }
12037
12038 /* end button procedures */
12039
12040 void
12041 PrintPosition(fp, move)
12042      FILE *fp;
12043      int move;
12044 {
12045     int i, j;
12046     
12047     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12048         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12049             char c = PieceToChar(boards[move][i][j]);
12050             fputc(c == 'x' ? '.' : c, fp);
12051             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12052         }
12053     }
12054     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12055       fprintf(fp, "white to play\n");
12056     else
12057       fprintf(fp, "black to play\n");
12058 }
12059
12060 void
12061 PrintOpponents(fp)
12062      FILE *fp;
12063 {
12064     if (gameInfo.white != NULL) {
12065         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12066     } else {
12067         fprintf(fp, "\n");
12068     }
12069 }
12070
12071 /* Find last component of program's own name, using some heuristics */
12072 void
12073 TidyProgramName(prog, host, buf)
12074      char *prog, *host, buf[MSG_SIZ];
12075 {
12076     char *p, *q;
12077     int local = (strcmp(host, "localhost") == 0);
12078     while (!local && (p = strchr(prog, ';')) != NULL) {
12079         p++;
12080         while (*p == ' ') p++;
12081         prog = p;
12082     }
12083     if (*prog == '"' || *prog == '\'') {
12084         q = strchr(prog + 1, *prog);
12085     } else {
12086         q = strchr(prog, ' ');
12087     }
12088     if (q == NULL) q = prog + strlen(prog);
12089     p = q;
12090     while (p >= prog && *p != '/' && *p != '\\') p--;
12091     p++;
12092     if(p == prog && *p == '"') p++;
12093     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12094     memcpy(buf, p, q - p);
12095     buf[q - p] = NULLCHAR;
12096     if (!local) {
12097         strcat(buf, "@");
12098         strcat(buf, host);
12099     }
12100 }
12101
12102 char *
12103 TimeControlTagValue()
12104 {
12105     char buf[MSG_SIZ];
12106     if (!appData.clockMode) {
12107         strcpy(buf, "-");
12108     } else if (movesPerSession > 0) {
12109         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12110     } else if (timeIncrement == 0) {
12111         sprintf(buf, "%ld", timeControl/1000);
12112     } else {
12113         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12114     }
12115     return StrSave(buf);
12116 }
12117
12118 void
12119 SetGameInfo()
12120 {
12121     /* This routine is used only for certain modes */
12122     VariantClass v = gameInfo.variant;
12123     ClearGameInfo(&gameInfo);
12124     gameInfo.variant = v;
12125
12126     switch (gameMode) {
12127       case MachinePlaysWhite:
12128         gameInfo.event = StrSave( appData.pgnEventHeader );
12129         gameInfo.site = StrSave(HostName());
12130         gameInfo.date = PGNDate();
12131         gameInfo.round = StrSave("-");
12132         gameInfo.white = StrSave(first.tidy);
12133         gameInfo.black = StrSave(UserName());
12134         gameInfo.timeControl = TimeControlTagValue();
12135         break;
12136
12137       case MachinePlaysBlack:
12138         gameInfo.event = StrSave( appData.pgnEventHeader );
12139         gameInfo.site = StrSave(HostName());
12140         gameInfo.date = PGNDate();
12141         gameInfo.round = StrSave("-");
12142         gameInfo.white = StrSave(UserName());
12143         gameInfo.black = StrSave(first.tidy);
12144         gameInfo.timeControl = TimeControlTagValue();
12145         break;
12146
12147       case TwoMachinesPlay:
12148         gameInfo.event = StrSave( appData.pgnEventHeader );
12149         gameInfo.site = StrSave(HostName());
12150         gameInfo.date = PGNDate();
12151         if (matchGame > 0) {
12152             char buf[MSG_SIZ];
12153             sprintf(buf, "%d", matchGame);
12154             gameInfo.round = StrSave(buf);
12155         } else {
12156             gameInfo.round = StrSave("-");
12157         }
12158         if (first.twoMachinesColor[0] == 'w') {
12159             gameInfo.white = StrSave(first.tidy);
12160             gameInfo.black = StrSave(second.tidy);
12161         } else {
12162             gameInfo.white = StrSave(second.tidy);
12163             gameInfo.black = StrSave(first.tidy);
12164         }
12165         gameInfo.timeControl = TimeControlTagValue();
12166         break;
12167
12168       case EditGame:
12169         gameInfo.event = StrSave("Edited game");
12170         gameInfo.site = StrSave(HostName());
12171         gameInfo.date = PGNDate();
12172         gameInfo.round = StrSave("-");
12173         gameInfo.white = StrSave("-");
12174         gameInfo.black = StrSave("-");
12175         break;
12176
12177       case EditPosition:
12178         gameInfo.event = StrSave("Edited position");
12179         gameInfo.site = StrSave(HostName());
12180         gameInfo.date = PGNDate();
12181         gameInfo.round = StrSave("-");
12182         gameInfo.white = StrSave("-");
12183         gameInfo.black = StrSave("-");
12184         break;
12185
12186       case IcsPlayingWhite:
12187       case IcsPlayingBlack:
12188       case IcsObserving:
12189       case IcsExamining:
12190         break;
12191
12192       case PlayFromGameFile:
12193         gameInfo.event = StrSave("Game from non-PGN file");
12194         gameInfo.site = StrSave(HostName());
12195         gameInfo.date = PGNDate();
12196         gameInfo.round = StrSave("-");
12197         gameInfo.white = StrSave("?");
12198         gameInfo.black = StrSave("?");
12199         break;
12200
12201       default:
12202         break;
12203     }
12204 }
12205
12206 void
12207 ReplaceComment(index, text)
12208      int index;
12209      char *text;
12210 {
12211     int len;
12212
12213     while (*text == '\n') text++;
12214     len = strlen(text);
12215     while (len > 0 && text[len - 1] == '\n') len--;
12216
12217     if (commentList[index] != NULL)
12218       free(commentList[index]);
12219
12220     if (len == 0) {
12221         commentList[index] = NULL;
12222         return;
12223     }
12224     commentList[index] = (char *) malloc(len + 2);
12225     strncpy(commentList[index], text, len);
12226     commentList[index][len] = '\n';
12227     commentList[index][len + 1] = NULLCHAR;
12228 }
12229
12230 void
12231 CrushCRs(text)
12232      char *text;
12233 {
12234   char *p = text;
12235   char *q = text;
12236   char ch;
12237
12238   do {
12239     ch = *p++;
12240     if (ch == '\r') continue;
12241     *q++ = ch;
12242   } while (ch != '\0');
12243 }
12244
12245 void
12246 AppendComment(index, text)
12247      int index;
12248      char *text;
12249 {
12250     int oldlen, len;
12251     char *old;
12252
12253     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12254
12255     CrushCRs(text);
12256     while (*text == '\n') text++;
12257     len = strlen(text);
12258     while (len > 0 && text[len - 1] == '\n') len--;
12259
12260     if (len == 0) return;
12261
12262     if (commentList[index] != NULL) {
12263         old = commentList[index];
12264         oldlen = strlen(old);
12265         commentList[index] = (char *) malloc(oldlen + len + 2);
12266         strcpy(commentList[index], old);
12267         free(old);
12268         strncpy(&commentList[index][oldlen], text, len);
12269         commentList[index][oldlen + len] = '\n';
12270         commentList[index][oldlen + len + 1] = NULLCHAR;
12271     } else {
12272         commentList[index] = (char *) malloc(len + 2);
12273         strncpy(commentList[index], text, len);
12274         commentList[index][len] = '\n';
12275         commentList[index][len + 1] = NULLCHAR;
12276     }
12277 }
12278
12279 static char * FindStr( char * text, char * sub_text )
12280 {
12281     char * result = strstr( text, sub_text );
12282
12283     if( result != NULL ) {
12284         result += strlen( sub_text );
12285     }
12286
12287     return result;
12288 }
12289
12290 /* [AS] Try to extract PV info from PGN comment */
12291 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12292 char *GetInfoFromComment( int index, char * text )
12293 {
12294     char * sep = text;
12295
12296     if( text != NULL && index > 0 ) {
12297         int score = 0;
12298         int depth = 0;
12299         int time = -1, sec = 0, deci;
12300         char * s_eval = FindStr( text, "[%eval " );
12301         char * s_emt = FindStr( text, "[%emt " );
12302
12303         if( s_eval != NULL || s_emt != NULL ) {
12304             /* New style */
12305             char delim;
12306
12307             if( s_eval != NULL ) {
12308                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12309                     return text;
12310                 }
12311
12312                 if( delim != ']' ) {
12313                     return text;
12314                 }
12315             }
12316
12317             if( s_emt != NULL ) {
12318             }
12319         }
12320         else {
12321             /* We expect something like: [+|-]nnn.nn/dd */
12322             int score_lo = 0;
12323
12324             sep = strchr( text, '/' );
12325             if( sep == NULL || sep < (text+4) ) {
12326                 return text;
12327             }
12328
12329             time = -1; sec = -1; deci = -1;
12330             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12331                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12332                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12333                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12334                 return text;
12335             }
12336
12337             if( score_lo < 0 || score_lo >= 100 ) {
12338                 return text;
12339             }
12340
12341             if(sec >= 0) time = 600*time + 10*sec; else
12342             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12343
12344             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12345
12346             /* [HGM] PV time: now locate end of PV info */
12347             while( *++sep >= '0' && *sep <= '9'); // strip depth
12348             if(time >= 0)
12349             while( *++sep >= '0' && *sep <= '9'); // strip time
12350             if(sec >= 0)
12351             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12352             if(deci >= 0)
12353             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12354             while(*sep == ' ') sep++;
12355         }
12356
12357         if( depth <= 0 ) {
12358             return text;
12359         }
12360
12361         if( time < 0 ) {
12362             time = -1;
12363         }
12364
12365         pvInfoList[index-1].depth = depth;
12366         pvInfoList[index-1].score = score;
12367         pvInfoList[index-1].time  = 10*time; // centi-sec
12368     }
12369     return sep;
12370 }
12371
12372 void
12373 SendToProgram(message, cps)
12374      char *message;
12375      ChessProgramState *cps;
12376 {
12377     int count, outCount, error;
12378     char buf[MSG_SIZ];
12379
12380     if (cps->pr == NULL) return;
12381     Attention(cps);
12382     
12383     if (appData.debugMode) {
12384         TimeMark now;
12385         GetTimeMark(&now);
12386         fprintf(debugFP, "%ld >%-6s: %s", 
12387                 SubtractTimeMarks(&now, &programStartTime),
12388                 cps->which, message);
12389     }
12390     
12391     count = strlen(message);
12392     outCount = OutputToProcess(cps->pr, message, count, &error);
12393     if (outCount < count && !exiting 
12394                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12395         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12396         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12397             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12398                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12399                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12400             } else {
12401                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12402             }
12403             gameInfo.resultDetails = buf;
12404         }
12405         DisplayFatalError(buf, error, 1);
12406     }
12407 }
12408
12409 void
12410 ReceiveFromProgram(isr, closure, message, count, error)
12411      InputSourceRef isr;
12412      VOIDSTAR closure;
12413      char *message;
12414      int count;
12415      int error;
12416 {
12417     char *end_str;
12418     char buf[MSG_SIZ];
12419     ChessProgramState *cps = (ChessProgramState *)closure;
12420
12421     if (isr != cps->isr) return; /* Killed intentionally */
12422     if (count <= 0) {
12423         if (count == 0) {
12424             sprintf(buf,
12425                     _("Error: %s chess program (%s) exited unexpectedly"),
12426                     cps->which, cps->program);
12427         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12428                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12429                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12430                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12431                 } else {
12432                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12433                 }
12434                 gameInfo.resultDetails = buf;
12435             }
12436             RemoveInputSource(cps->isr);
12437             DisplayFatalError(buf, 0, 1);
12438         } else {
12439             sprintf(buf,
12440                     _("Error reading from %s chess program (%s)"),
12441                     cps->which, cps->program);
12442             RemoveInputSource(cps->isr);
12443
12444             /* [AS] Program is misbehaving badly... kill it */
12445             if( count == -2 ) {
12446                 DestroyChildProcess( cps->pr, 9 );
12447                 cps->pr = NoProc;
12448             }
12449
12450             DisplayFatalError(buf, error, 1);
12451         }
12452         return;
12453     }
12454     
12455     if ((end_str = strchr(message, '\r')) != NULL)
12456       *end_str = NULLCHAR;
12457     if ((end_str = strchr(message, '\n')) != NULL)
12458       *end_str = NULLCHAR;
12459     
12460     if (appData.debugMode) {
12461         TimeMark now; int print = 1;
12462         char *quote = ""; char c; int i;
12463
12464         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12465                 char start = message[0];
12466                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12467                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12468                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12469                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12470                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12471                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12472                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12473                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12474                         { quote = "# "; print = (appData.engineComments == 2); }
12475                 message[0] = start; // restore original message
12476         }
12477         if(print) {
12478                 GetTimeMark(&now);
12479                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12480                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12481                         quote,
12482                         message);
12483         }
12484     }
12485
12486     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12487     if (appData.icsEngineAnalyze) {
12488         if (strstr(message, "whisper") != NULL ||
12489              strstr(message, "kibitz") != NULL || 
12490             strstr(message, "tellics") != NULL) return;
12491     }
12492
12493     HandleMachineMove(message, cps);
12494 }
12495
12496
12497 void
12498 SendTimeControl(cps, mps, tc, inc, sd, st)
12499      ChessProgramState *cps;
12500      int mps, inc, sd, st;
12501      long tc;
12502 {
12503     char buf[MSG_SIZ];
12504     int seconds;
12505
12506     if( timeControl_2 > 0 ) {
12507         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12508             tc = timeControl_2;
12509         }
12510     }
12511     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12512     inc /= cps->timeOdds;
12513     st  /= cps->timeOdds;
12514
12515     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12516
12517     if (st > 0) {
12518       /* Set exact time per move, normally using st command */
12519       if (cps->stKludge) {
12520         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12521         seconds = st % 60;
12522         if (seconds == 0) {
12523           sprintf(buf, "level 1 %d\n", st/60);
12524         } else {
12525           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12526         }
12527       } else {
12528         sprintf(buf, "st %d\n", st);
12529       }
12530     } else {
12531       /* Set conventional or incremental time control, using level command */
12532       if (seconds == 0) {
12533         /* Note old gnuchess bug -- minutes:seconds used to not work.
12534            Fixed in later versions, but still avoid :seconds
12535            when seconds is 0. */
12536         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12537       } else {
12538         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12539                 seconds, inc/1000);
12540       }
12541     }
12542     SendToProgram(buf, cps);
12543
12544     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12545     /* Orthogonally, limit search to given depth */
12546     if (sd > 0) {
12547       if (cps->sdKludge) {
12548         sprintf(buf, "depth\n%d\n", sd);
12549       } else {
12550         sprintf(buf, "sd %d\n", sd);
12551       }
12552       SendToProgram(buf, cps);
12553     }
12554
12555     if(cps->nps > 0) { /* [HGM] nps */
12556         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12557         else {
12558                 sprintf(buf, "nps %d\n", cps->nps);
12559               SendToProgram(buf, cps);
12560         }
12561     }
12562 }
12563
12564 ChessProgramState *WhitePlayer()
12565 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12566 {
12567     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12568        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12569         return &second;
12570     return &first;
12571 }
12572
12573 void
12574 SendTimeRemaining(cps, machineWhite)
12575      ChessProgramState *cps;
12576      int /*boolean*/ machineWhite;
12577 {
12578     char message[MSG_SIZ];
12579     long time, otime;
12580
12581     /* Note: this routine must be called when the clocks are stopped
12582        or when they have *just* been set or switched; otherwise
12583        it will be off by the time since the current tick started.
12584     */
12585     if (machineWhite) {
12586         time = whiteTimeRemaining / 10;
12587         otime = blackTimeRemaining / 10;
12588     } else {
12589         time = blackTimeRemaining / 10;
12590         otime = whiteTimeRemaining / 10;
12591     }
12592     /* [HGM] translate opponent's time by time-odds factor */
12593     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12594     if (appData.debugMode) {
12595         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12596     }
12597
12598     if (time <= 0) time = 1;
12599     if (otime <= 0) otime = 1;
12600     
12601     sprintf(message, "time %ld\n", time);
12602     SendToProgram(message, cps);
12603
12604     sprintf(message, "otim %ld\n", otime);
12605     SendToProgram(message, cps);
12606 }
12607
12608 int
12609 BoolFeature(p, name, loc, cps)
12610      char **p;
12611      char *name;
12612      int *loc;
12613      ChessProgramState *cps;
12614 {
12615   char buf[MSG_SIZ];
12616   int len = strlen(name);
12617   int val;
12618   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12619     (*p) += len + 1;
12620     sscanf(*p, "%d", &val);
12621     *loc = (val != 0);
12622     while (**p && **p != ' ') (*p)++;
12623     sprintf(buf, "accepted %s\n", name);
12624     SendToProgram(buf, cps);
12625     return TRUE;
12626   }
12627   return FALSE;
12628 }
12629
12630 int
12631 IntFeature(p, name, loc, cps)
12632      char **p;
12633      char *name;
12634      int *loc;
12635      ChessProgramState *cps;
12636 {
12637   char buf[MSG_SIZ];
12638   int len = strlen(name);
12639   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12640     (*p) += len + 1;
12641     sscanf(*p, "%d", loc);
12642     while (**p && **p != ' ') (*p)++;
12643     sprintf(buf, "accepted %s\n", name);
12644     SendToProgram(buf, cps);
12645     return TRUE;
12646   }
12647   return FALSE;
12648 }
12649
12650 int
12651 StringFeature(p, name, loc, cps)
12652      char **p;
12653      char *name;
12654      char loc[];
12655      ChessProgramState *cps;
12656 {
12657   char buf[MSG_SIZ];
12658   int len = strlen(name);
12659   if (strncmp((*p), name, len) == 0
12660       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12661     (*p) += len + 2;
12662     sscanf(*p, "%[^\"]", loc);
12663     while (**p && **p != '\"') (*p)++;
12664     if (**p == '\"') (*p)++;
12665     sprintf(buf, "accepted %s\n", name);
12666     SendToProgram(buf, cps);
12667     return TRUE;
12668   }
12669   return FALSE;
12670 }
12671
12672 int 
12673 ParseOption(Option *opt, ChessProgramState *cps)
12674 // [HGM] options: process the string that defines an engine option, and determine
12675 // name, type, default value, and allowed value range
12676 {
12677         char *p, *q, buf[MSG_SIZ];
12678         int n, min = (-1)<<31, max = 1<<31, def;
12679
12680         if(p = strstr(opt->name, " -spin ")) {
12681             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12682             if(max < min) max = min; // enforce consistency
12683             if(def < min) def = min;
12684             if(def > max) def = max;
12685             opt->value = def;
12686             opt->min = min;
12687             opt->max = max;
12688             opt->type = Spin;
12689         } else if((p = strstr(opt->name, " -slider "))) {
12690             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12691             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12692             if(max < min) max = min; // enforce consistency
12693             if(def < min) def = min;
12694             if(def > max) def = max;
12695             opt->value = def;
12696             opt->min = min;
12697             opt->max = max;
12698             opt->type = Spin; // Slider;
12699         } else if((p = strstr(opt->name, " -string "))) {
12700             opt->textValue = p+9;
12701             opt->type = TextBox;
12702         } else if((p = strstr(opt->name, " -file "))) {
12703             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12704             opt->textValue = p+7;
12705             opt->type = TextBox; // FileName;
12706         } else if((p = strstr(opt->name, " -path "))) {
12707             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12708             opt->textValue = p+7;
12709             opt->type = TextBox; // PathName;
12710         } else if(p = strstr(opt->name, " -check ")) {
12711             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12712             opt->value = (def != 0);
12713             opt->type = CheckBox;
12714         } else if(p = strstr(opt->name, " -combo ")) {
12715             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12716             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12717             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12718             opt->value = n = 0;
12719             while(q = StrStr(q, " /// ")) {
12720                 n++; *q = 0;    // count choices, and null-terminate each of them
12721                 q += 5;
12722                 if(*q == '*') { // remember default, which is marked with * prefix
12723                     q++;
12724                     opt->value = n;
12725                 }
12726                 cps->comboList[cps->comboCnt++] = q;
12727             }
12728             cps->comboList[cps->comboCnt++] = NULL;
12729             opt->max = n + 1;
12730             opt->type = ComboBox;
12731         } else if(p = strstr(opt->name, " -button")) {
12732             opt->type = Button;
12733         } else if(p = strstr(opt->name, " -save")) {
12734             opt->type = SaveButton;
12735         } else return FALSE;
12736         *p = 0; // terminate option name
12737         // now look if the command-line options define a setting for this engine option.
12738         if(cps->optionSettings && cps->optionSettings[0])
12739             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12740         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12741                 sprintf(buf, "option %s", p);
12742                 if(p = strstr(buf, ",")) *p = 0;
12743                 strcat(buf, "\n");
12744                 SendToProgram(buf, cps);
12745         }
12746         return TRUE;
12747 }
12748
12749 void
12750 FeatureDone(cps, val)
12751      ChessProgramState* cps;
12752      int val;
12753 {
12754   DelayedEventCallback cb = GetDelayedEvent();
12755   if ((cb == InitBackEnd3 && cps == &first) ||
12756       (cb == TwoMachinesEventIfReady && cps == &second)) {
12757     CancelDelayedEvent();
12758     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12759   }
12760   cps->initDone = val;
12761 }
12762
12763 /* Parse feature command from engine */
12764 void
12765 ParseFeatures(args, cps)
12766      char* args;
12767      ChessProgramState *cps;  
12768 {
12769   char *p = args;
12770   char *q;
12771   int val;
12772   char buf[MSG_SIZ];
12773
12774   for (;;) {
12775     while (*p == ' ') p++;
12776     if (*p == NULLCHAR) return;
12777
12778     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12779     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12780     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12781     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12782     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12783     if (BoolFeature(&p, "reuse", &val, cps)) {
12784       /* Engine can disable reuse, but can't enable it if user said no */
12785       if (!val) cps->reuse = FALSE;
12786       continue;
12787     }
12788     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12789     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12790       if (gameMode == TwoMachinesPlay) {
12791         DisplayTwoMachinesTitle();
12792       } else {
12793         DisplayTitle("");
12794       }
12795       continue;
12796     }
12797     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12798     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12799     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12800     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12801     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12802     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12803     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12804     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12805     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12806     if (IntFeature(&p, "done", &val, cps)) {
12807       FeatureDone(cps, val);
12808       continue;
12809     }
12810     /* Added by Tord: */
12811     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12812     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12813     /* End of additions by Tord */
12814
12815     /* [HGM] added features: */
12816     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12817     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12818     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12819     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12820     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12821     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12822     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12823         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12824             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12825             SendToProgram(buf, cps);
12826             continue;
12827         }
12828         if(cps->nrOptions >= MAX_OPTIONS) {
12829             cps->nrOptions--;
12830             sprintf(buf, "%s engine has too many options\n", cps->which);
12831             DisplayError(buf, 0);
12832         }
12833         continue;
12834     }
12835     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12836     /* End of additions by HGM */
12837
12838     /* unknown feature: complain and skip */
12839     q = p;
12840     while (*q && *q != '=') q++;
12841     sprintf(buf, "rejected %.*s\n", q-p, p);
12842     SendToProgram(buf, cps);
12843     p = q;
12844     if (*p == '=') {
12845       p++;
12846       if (*p == '\"') {
12847         p++;
12848         while (*p && *p != '\"') p++;
12849         if (*p == '\"') p++;
12850       } else {
12851         while (*p && *p != ' ') p++;
12852       }
12853     }
12854   }
12855
12856 }
12857
12858 void
12859 PeriodicUpdatesEvent(newState)
12860      int newState;
12861 {
12862     if (newState == appData.periodicUpdates)
12863       return;
12864
12865     appData.periodicUpdates=newState;
12866
12867     /* Display type changes, so update it now */
12868 //    DisplayAnalysis();
12869
12870     /* Get the ball rolling again... */
12871     if (newState) {
12872         AnalysisPeriodicEvent(1);
12873         StartAnalysisClock();
12874     }
12875 }
12876
12877 void
12878 PonderNextMoveEvent(newState)
12879      int newState;
12880 {
12881     if (newState == appData.ponderNextMove) return;
12882     if (gameMode == EditPosition) EditPositionDone();
12883     if (newState) {
12884         SendToProgram("hard\n", &first);
12885         if (gameMode == TwoMachinesPlay) {
12886             SendToProgram("hard\n", &second);
12887         }
12888     } else {
12889         SendToProgram("easy\n", &first);
12890         thinkOutput[0] = NULLCHAR;
12891         if (gameMode == TwoMachinesPlay) {
12892             SendToProgram("easy\n", &second);
12893         }
12894     }
12895     appData.ponderNextMove = newState;
12896 }
12897
12898 void
12899 NewSettingEvent(option, command, value)
12900      char *command;
12901      int option, value;
12902 {
12903     char buf[MSG_SIZ];
12904
12905     if (gameMode == EditPosition) EditPositionDone();
12906     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12907     SendToProgram(buf, &first);
12908     if (gameMode == TwoMachinesPlay) {
12909         SendToProgram(buf, &second);
12910     }
12911 }
12912
12913 void
12914 ShowThinkingEvent()
12915 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12916 {
12917     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12918     int newState = appData.showThinking
12919         // [HGM] thinking: other features now need thinking output as well
12920         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12921     
12922     if (oldState == newState) return;
12923     oldState = newState;
12924     if (gameMode == EditPosition) EditPositionDone();
12925     if (oldState) {
12926         SendToProgram("post\n", &first);
12927         if (gameMode == TwoMachinesPlay) {
12928             SendToProgram("post\n", &second);
12929         }
12930     } else {
12931         SendToProgram("nopost\n", &first);
12932         thinkOutput[0] = NULLCHAR;
12933         if (gameMode == TwoMachinesPlay) {
12934             SendToProgram("nopost\n", &second);
12935         }
12936     }
12937 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12938 }
12939
12940 void
12941 AskQuestionEvent(title, question, replyPrefix, which)
12942      char *title; char *question; char *replyPrefix; char *which;
12943 {
12944   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12945   if (pr == NoProc) return;
12946   AskQuestion(title, question, replyPrefix, pr);
12947 }
12948
12949 void
12950 DisplayMove(moveNumber)
12951      int moveNumber;
12952 {
12953     char message[MSG_SIZ];
12954     char res[MSG_SIZ];
12955     char cpThinkOutput[MSG_SIZ];
12956
12957     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12958     
12959     if (moveNumber == forwardMostMove - 1 || 
12960         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12961
12962         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12963
12964         if (strchr(cpThinkOutput, '\n')) {
12965             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12966         }
12967     } else {
12968         *cpThinkOutput = NULLCHAR;
12969     }
12970
12971     /* [AS] Hide thinking from human user */
12972     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12973         *cpThinkOutput = NULLCHAR;
12974         if( thinkOutput[0] != NULLCHAR ) {
12975             int i;
12976
12977             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12978                 cpThinkOutput[i] = '.';
12979             }
12980             cpThinkOutput[i] = NULLCHAR;
12981             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12982         }
12983     }
12984
12985     if (moveNumber == forwardMostMove - 1 &&
12986         gameInfo.resultDetails != NULL) {
12987         if (gameInfo.resultDetails[0] == NULLCHAR) {
12988             sprintf(res, " %s", PGNResult(gameInfo.result));
12989         } else {
12990             sprintf(res, " {%s} %s",
12991                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12992         }
12993     } else {
12994         res[0] = NULLCHAR;
12995     }
12996
12997     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12998         DisplayMessage(res, cpThinkOutput);
12999     } else {
13000         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13001                 WhiteOnMove(moveNumber) ? " " : ".. ",
13002                 parseList[moveNumber], res);
13003         DisplayMessage(message, cpThinkOutput);
13004     }
13005 }
13006
13007 void
13008 DisplayComment(moveNumber, text)
13009      int moveNumber;
13010      char *text;
13011 {
13012     char title[MSG_SIZ];
13013     char buf[8000]; // comment can be long!
13014     int score, depth;
13015
13016     if( appData.autoDisplayComment ) {
13017         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13018             strcpy(title, "Comment");
13019         } else {
13020             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13021                     WhiteOnMove(moveNumber) ? " " : ".. ",
13022                     parseList[moveNumber]);
13023         }
13024         // [HGM] PV info: display PV info together with (or as) comment
13025         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13026             if(text == NULL) text = "";                                           
13027             score = pvInfoList[moveNumber].score;
13028             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13029                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13030             text = buf;
13031         }
13032     } else title[0] = 0;
13033
13034     if (text != NULL)
13035         CommentPopUp(title, text);
13036 }
13037
13038 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13039  * might be busy thinking or pondering.  It can be omitted if your
13040  * gnuchess is configured to stop thinking immediately on any user
13041  * input.  However, that gnuchess feature depends on the FIONREAD
13042  * ioctl, which does not work properly on some flavors of Unix.
13043  */
13044 void
13045 Attention(cps)
13046      ChessProgramState *cps;
13047 {
13048 #if ATTENTION
13049     if (!cps->useSigint) return;
13050     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13051     switch (gameMode) {
13052       case MachinePlaysWhite:
13053       case MachinePlaysBlack:
13054       case TwoMachinesPlay:
13055       case IcsPlayingWhite:
13056       case IcsPlayingBlack:
13057       case AnalyzeMode:
13058       case AnalyzeFile:
13059         /* Skip if we know it isn't thinking */
13060         if (!cps->maybeThinking) return;
13061         if (appData.debugMode)
13062           fprintf(debugFP, "Interrupting %s\n", cps->which);
13063         InterruptChildProcess(cps->pr);
13064         cps->maybeThinking = FALSE;
13065         break;
13066       default:
13067         break;
13068     }
13069 #endif /*ATTENTION*/
13070 }
13071
13072 int
13073 CheckFlags()
13074 {
13075     if (whiteTimeRemaining <= 0) {
13076         if (!whiteFlag) {
13077             whiteFlag = TRUE;
13078             if (appData.icsActive) {
13079                 if (appData.autoCallFlag &&
13080                     gameMode == IcsPlayingBlack && !blackFlag) {
13081                   SendToICS(ics_prefix);
13082                   SendToICS("flag\n");
13083                 }
13084             } else {
13085                 if (blackFlag) {
13086                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13087                 } else {
13088                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13089                     if (appData.autoCallFlag) {
13090                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13091                         return TRUE;
13092                     }
13093                 }
13094             }
13095         }
13096     }
13097     if (blackTimeRemaining <= 0) {
13098         if (!blackFlag) {
13099             blackFlag = TRUE;
13100             if (appData.icsActive) {
13101                 if (appData.autoCallFlag &&
13102                     gameMode == IcsPlayingWhite && !whiteFlag) {
13103                   SendToICS(ics_prefix);
13104                   SendToICS("flag\n");
13105                 }
13106             } else {
13107                 if (whiteFlag) {
13108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13109                 } else {
13110                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13111                     if (appData.autoCallFlag) {
13112                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13113                         return TRUE;
13114                     }
13115                 }
13116             }
13117         }
13118     }
13119     return FALSE;
13120 }
13121
13122 void
13123 CheckTimeControl()
13124 {
13125     if (!appData.clockMode || appData.icsActive ||
13126         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13127
13128     /*
13129      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13130      */
13131     if ( !WhiteOnMove(forwardMostMove) )
13132         /* White made time control */
13133         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13134         /* [HGM] time odds: correct new time quota for time odds! */
13135                                             / WhitePlayer()->timeOdds;
13136       else
13137         /* Black made time control */
13138         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13139                                             / WhitePlayer()->other->timeOdds;
13140 }
13141
13142 void
13143 DisplayBothClocks()
13144 {
13145     int wom = gameMode == EditPosition ?
13146       !blackPlaysFirst : WhiteOnMove(currentMove);
13147     DisplayWhiteClock(whiteTimeRemaining, wom);
13148     DisplayBlackClock(blackTimeRemaining, !wom);
13149 }
13150
13151
13152 /* Timekeeping seems to be a portability nightmare.  I think everyone
13153    has ftime(), but I'm really not sure, so I'm including some ifdefs
13154    to use other calls if you don't.  Clocks will be less accurate if
13155    you have neither ftime nor gettimeofday.
13156 */
13157
13158 /* VS 2008 requires the #include outside of the function */
13159 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13160 #include <sys/timeb.h>
13161 #endif
13162
13163 /* Get the current time as a TimeMark */
13164 void
13165 GetTimeMark(tm)
13166      TimeMark *tm;
13167 {
13168 #if HAVE_GETTIMEOFDAY
13169
13170     struct timeval timeVal;
13171     struct timezone timeZone;
13172
13173     gettimeofday(&timeVal, &timeZone);
13174     tm->sec = (long) timeVal.tv_sec; 
13175     tm->ms = (int) (timeVal.tv_usec / 1000L);
13176
13177 #else /*!HAVE_GETTIMEOFDAY*/
13178 #if HAVE_FTIME
13179
13180 // include <sys/timeb.h> / moved to just above start of function
13181     struct timeb timeB;
13182
13183     ftime(&timeB);
13184     tm->sec = (long) timeB.time;
13185     tm->ms = (int) timeB.millitm;
13186
13187 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13188     tm->sec = (long) time(NULL);
13189     tm->ms = 0;
13190 #endif
13191 #endif
13192 }
13193
13194 /* Return the difference in milliseconds between two
13195    time marks.  We assume the difference will fit in a long!
13196 */
13197 long
13198 SubtractTimeMarks(tm2, tm1)
13199      TimeMark *tm2, *tm1;
13200 {
13201     return 1000L*(tm2->sec - tm1->sec) +
13202            (long) (tm2->ms - tm1->ms);
13203 }
13204
13205
13206 /*
13207  * Code to manage the game clocks.
13208  *
13209  * In tournament play, black starts the clock and then white makes a move.
13210  * We give the human user a slight advantage if he is playing white---the
13211  * clocks don't run until he makes his first move, so it takes zero time.
13212  * Also, we don't account for network lag, so we could get out of sync
13213  * with GNU Chess's clock -- but then, referees are always right.  
13214  */
13215
13216 static TimeMark tickStartTM;
13217 static long intendedTickLength;
13218
13219 long
13220 NextTickLength(timeRemaining)
13221      long timeRemaining;
13222 {
13223     long nominalTickLength, nextTickLength;
13224
13225     if (timeRemaining > 0L && timeRemaining <= 10000L)
13226       nominalTickLength = 100L;
13227     else
13228       nominalTickLength = 1000L;
13229     nextTickLength = timeRemaining % nominalTickLength;
13230     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13231
13232     return nextTickLength;
13233 }
13234
13235 /* Adjust clock one minute up or down */
13236 void
13237 AdjustClock(Boolean which, int dir)
13238 {
13239     if(which) blackTimeRemaining += 60000*dir;
13240     else      whiteTimeRemaining += 60000*dir;
13241     DisplayBothClocks();
13242 }
13243
13244 /* Stop clocks and reset to a fresh time control */
13245 void
13246 ResetClocks() 
13247 {
13248     (void) StopClockTimer();
13249     if (appData.icsActive) {
13250         whiteTimeRemaining = blackTimeRemaining = 0;
13251     } else { /* [HGM] correct new time quote for time odds */
13252         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13253         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13254     }
13255     if (whiteFlag || blackFlag) {
13256         DisplayTitle("");
13257         whiteFlag = blackFlag = FALSE;
13258     }
13259     DisplayBothClocks();
13260 }
13261
13262 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13263
13264 /* Decrement running clock by amount of time that has passed */
13265 void
13266 DecrementClocks()
13267 {
13268     long timeRemaining;
13269     long lastTickLength, fudge;
13270     TimeMark now;
13271
13272     if (!appData.clockMode) return;
13273     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13274         
13275     GetTimeMark(&now);
13276
13277     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13278
13279     /* Fudge if we woke up a little too soon */
13280     fudge = intendedTickLength - lastTickLength;
13281     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13282
13283     if (WhiteOnMove(forwardMostMove)) {
13284         if(whiteNPS >= 0) lastTickLength = 0;
13285         timeRemaining = whiteTimeRemaining -= lastTickLength;
13286         DisplayWhiteClock(whiteTimeRemaining - fudge,
13287                           WhiteOnMove(currentMove));
13288     } else {
13289         if(blackNPS >= 0) lastTickLength = 0;
13290         timeRemaining = blackTimeRemaining -= lastTickLength;
13291         DisplayBlackClock(blackTimeRemaining - fudge,
13292                           !WhiteOnMove(currentMove));
13293     }
13294
13295     if (CheckFlags()) return;
13296         
13297     tickStartTM = now;
13298     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13299     StartClockTimer(intendedTickLength);
13300
13301     /* if the time remaining has fallen below the alarm threshold, sound the
13302      * alarm. if the alarm has sounded and (due to a takeback or time control
13303      * with increment) the time remaining has increased to a level above the
13304      * threshold, reset the alarm so it can sound again. 
13305      */
13306     
13307     if (appData.icsActive && appData.icsAlarm) {
13308
13309         /* make sure we are dealing with the user's clock */
13310         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13311                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13312            )) return;
13313
13314         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13315             alarmSounded = FALSE;
13316         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13317             PlayAlarmSound();
13318             alarmSounded = TRUE;
13319         }
13320     }
13321 }
13322
13323
13324 /* A player has just moved, so stop the previously running
13325    clock and (if in clock mode) start the other one.
13326    We redisplay both clocks in case we're in ICS mode, because
13327    ICS gives us an update to both clocks after every move.
13328    Note that this routine is called *after* forwardMostMove
13329    is updated, so the last fractional tick must be subtracted
13330    from the color that is *not* on move now.
13331 */
13332 void
13333 SwitchClocks()
13334 {
13335     long lastTickLength;
13336     TimeMark now;
13337     int flagged = FALSE;
13338
13339     GetTimeMark(&now);
13340
13341     if (StopClockTimer() && appData.clockMode) {
13342         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13343         if (WhiteOnMove(forwardMostMove)) {
13344             if(blackNPS >= 0) lastTickLength = 0;
13345             blackTimeRemaining -= lastTickLength;
13346            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13347 //         if(pvInfoList[forwardMostMove-1].time == -1)
13348                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13349                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13350         } else {
13351            if(whiteNPS >= 0) lastTickLength = 0;
13352            whiteTimeRemaining -= 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 = 
13356                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13357         }
13358         flagged = CheckFlags();
13359     }
13360     CheckTimeControl();
13361
13362     if (flagged || !appData.clockMode) return;
13363
13364     switch (gameMode) {
13365       case MachinePlaysBlack:
13366       case MachinePlaysWhite:
13367       case BeginningOfGame:
13368         if (pausing) return;
13369         break;
13370
13371       case EditGame:
13372       case PlayFromGameFile:
13373       case IcsExamining:
13374         return;
13375
13376       default:
13377         break;
13378     }
13379
13380     tickStartTM = now;
13381     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13382       whiteTimeRemaining : blackTimeRemaining);
13383     StartClockTimer(intendedTickLength);
13384 }
13385         
13386
13387 /* Stop both clocks */
13388 void
13389 StopClocks()
13390 {       
13391     long lastTickLength;
13392     TimeMark now;
13393
13394     if (!StopClockTimer()) return;
13395     if (!appData.clockMode) return;
13396
13397     GetTimeMark(&now);
13398
13399     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13400     if (WhiteOnMove(forwardMostMove)) {
13401         if(whiteNPS >= 0) lastTickLength = 0;
13402         whiteTimeRemaining -= lastTickLength;
13403         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13404     } else {
13405         if(blackNPS >= 0) lastTickLength = 0;
13406         blackTimeRemaining -= lastTickLength;
13407         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13408     }
13409     CheckFlags();
13410 }
13411         
13412 /* Start clock of player on move.  Time may have been reset, so
13413    if clock is already running, stop and restart it. */
13414 void
13415 StartClocks()
13416 {
13417     (void) StopClockTimer(); /* in case it was running already */
13418     DisplayBothClocks();
13419     if (CheckFlags()) return;
13420
13421     if (!appData.clockMode) return;
13422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13423
13424     GetTimeMark(&tickStartTM);
13425     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13426       whiteTimeRemaining : blackTimeRemaining);
13427
13428    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13429     whiteNPS = blackNPS = -1; 
13430     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13431        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13432         whiteNPS = first.nps;
13433     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13434        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13435         blackNPS = first.nps;
13436     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13437         whiteNPS = second.nps;
13438     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13439         blackNPS = second.nps;
13440     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13441
13442     StartClockTimer(intendedTickLength);
13443 }
13444
13445 char *
13446 TimeString(ms)
13447      long ms;
13448 {
13449     long second, minute, hour, day;
13450     char *sign = "";
13451     static char buf[32];
13452     
13453     if (ms > 0 && ms <= 9900) {
13454       /* convert milliseconds to tenths, rounding up */
13455       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13456
13457       sprintf(buf, " %03.1f ", tenths/10.0);
13458       return buf;
13459     }
13460
13461     /* convert milliseconds to seconds, rounding up */
13462     /* use floating point to avoid strangeness of integer division
13463        with negative dividends on many machines */
13464     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13465
13466     if (second < 0) {
13467         sign = "-";
13468         second = -second;
13469     }
13470     
13471     day = second / (60 * 60 * 24);
13472     second = second % (60 * 60 * 24);
13473     hour = second / (60 * 60);
13474     second = second % (60 * 60);
13475     minute = second / 60;
13476     second = second % 60;
13477     
13478     if (day > 0)
13479       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13480               sign, day, hour, minute, second);
13481     else if (hour > 0)
13482       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13483     else
13484       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13485     
13486     return buf;
13487 }
13488
13489
13490 /*
13491  * This is necessary because some C libraries aren't ANSI C compliant yet.
13492  */
13493 char *
13494 StrStr(string, match)
13495      char *string, *match;
13496 {
13497     int i, length;
13498     
13499     length = strlen(match);
13500     
13501     for (i = strlen(string) - length; i >= 0; i--, string++)
13502       if (!strncmp(match, string, length))
13503         return string;
13504     
13505     return NULL;
13506 }
13507
13508 char *
13509 StrCaseStr(string, match)
13510      char *string, *match;
13511 {
13512     int i, j, length;
13513     
13514     length = strlen(match);
13515     
13516     for (i = strlen(string) - length; i >= 0; i--, string++) {
13517         for (j = 0; j < length; j++) {
13518             if (ToLower(match[j]) != ToLower(string[j]))
13519               break;
13520         }
13521         if (j == length) return string;
13522     }
13523
13524     return NULL;
13525 }
13526
13527 #ifndef _amigados
13528 int
13529 StrCaseCmp(s1, s2)
13530      char *s1, *s2;
13531 {
13532     char c1, c2;
13533     
13534     for (;;) {
13535         c1 = ToLower(*s1++);
13536         c2 = ToLower(*s2++);
13537         if (c1 > c2) return 1;
13538         if (c1 < c2) return -1;
13539         if (c1 == NULLCHAR) return 0;
13540     }
13541 }
13542
13543
13544 int
13545 ToLower(c)
13546      int c;
13547 {
13548     return isupper(c) ? tolower(c) : c;
13549 }
13550
13551
13552 int
13553 ToUpper(c)
13554      int c;
13555 {
13556     return islower(c) ? toupper(c) : c;
13557 }
13558 #endif /* !_amigados    */
13559
13560 char *
13561 StrSave(s)
13562      char *s;
13563 {
13564     char *ret;
13565
13566     if ((ret = (char *) malloc(strlen(s) + 1))) {
13567         strcpy(ret, s);
13568     }
13569     return ret;
13570 }
13571
13572 char *
13573 StrSavePtr(s, savePtr)
13574      char *s, **savePtr;
13575 {
13576     if (*savePtr) {
13577         free(*savePtr);
13578     }
13579     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13580         strcpy(*savePtr, s);
13581     }
13582     return(*savePtr);
13583 }
13584
13585 char *
13586 PGNDate()
13587 {
13588     time_t clock;
13589     struct tm *tm;
13590     char buf[MSG_SIZ];
13591
13592     clock = time((time_t *)NULL);
13593     tm = localtime(&clock);
13594     sprintf(buf, "%04d.%02d.%02d",
13595             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13596     return StrSave(buf);
13597 }
13598
13599
13600 char *
13601 PositionToFEN(move, overrideCastling)
13602      int move;
13603      char *overrideCastling;
13604 {
13605     int i, j, fromX, fromY, toX, toY;
13606     int whiteToPlay;
13607     char buf[128];
13608     char *p, *q;
13609     int emptycount;
13610     ChessSquare piece;
13611
13612     whiteToPlay = (gameMode == EditPosition) ?
13613       !blackPlaysFirst : (move % 2 == 0);
13614     p = buf;
13615
13616     /* Piece placement data */
13617     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13618         emptycount = 0;
13619         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13620             if (boards[move][i][j] == EmptySquare) {
13621                 emptycount++;
13622             } else { ChessSquare piece = boards[move][i][j];
13623                 if (emptycount > 0) {
13624                     if(emptycount<10) /* [HGM] can be >= 10 */
13625                         *p++ = '0' + emptycount;
13626                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13627                     emptycount = 0;
13628                 }
13629                 if(PieceToChar(piece) == '+') {
13630                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13631                     *p++ = '+';
13632                     piece = (ChessSquare)(DEMOTED piece);
13633                 } 
13634                 *p++ = PieceToChar(piece);
13635                 if(p[-1] == '~') {
13636                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13637                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13638                     *p++ = '~';
13639                 }
13640             }
13641         }
13642         if (emptycount > 0) {
13643             if(emptycount<10) /* [HGM] can be >= 10 */
13644                 *p++ = '0' + emptycount;
13645             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13646             emptycount = 0;
13647         }
13648         *p++ = '/';
13649     }
13650     *(p - 1) = ' ';
13651
13652     /* [HGM] print Crazyhouse or Shogi holdings */
13653     if( gameInfo.holdingsWidth ) {
13654         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13655         q = p;
13656         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13657             piece = boards[move][i][BOARD_WIDTH-1];
13658             if( piece != EmptySquare )
13659               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13660                   *p++ = PieceToChar(piece);
13661         }
13662         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13663             piece = boards[move][BOARD_HEIGHT-i-1][0];
13664             if( piece != EmptySquare )
13665               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13666                   *p++ = PieceToChar(piece);
13667         }
13668
13669         if( q == p ) *p++ = '-';
13670         *p++ = ']';
13671         *p++ = ' ';
13672     }
13673
13674     /* Active color */
13675     *p++ = whiteToPlay ? 'w' : 'b';
13676     *p++ = ' ';
13677
13678   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13679     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13680   } else {
13681   if(nrCastlingRights) {
13682      q = p;
13683      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13684        /* [HGM] write directly from rights */
13685            if(castlingRights[move][2] >= 0 &&
13686               castlingRights[move][0] >= 0   )
13687                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13688            if(castlingRights[move][2] >= 0 &&
13689               castlingRights[move][1] >= 0   )
13690                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13691            if(castlingRights[move][5] >= 0 &&
13692               castlingRights[move][3] >= 0   )
13693                 *p++ = castlingRights[move][3] + AAA;
13694            if(castlingRights[move][5] >= 0 &&
13695               castlingRights[move][4] >= 0   )
13696                 *p++ = castlingRights[move][4] + AAA;
13697      } else {
13698
13699         /* [HGM] write true castling rights */
13700         if( nrCastlingRights == 6 ) {
13701             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13702                castlingRights[move][2] >= 0  ) *p++ = 'K';
13703             if(castlingRights[move][1] == BOARD_LEFT &&
13704                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13705             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13706                castlingRights[move][5] >= 0  ) *p++ = 'k';
13707             if(castlingRights[move][4] == BOARD_LEFT &&
13708                castlingRights[move][5] >= 0  ) *p++ = 'q';
13709         }
13710      }
13711      if (q == p) *p++ = '-'; /* No castling rights */
13712      *p++ = ' ';
13713   }
13714
13715   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13716      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13717     /* En passant target square */
13718     if (move > backwardMostMove) {
13719         fromX = moveList[move - 1][0] - AAA;
13720         fromY = moveList[move - 1][1] - ONE;
13721         toX = moveList[move - 1][2] - AAA;
13722         toY = moveList[move - 1][3] - ONE;
13723         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13724             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13725             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13726             fromX == toX) {
13727             /* 2-square pawn move just happened */
13728             *p++ = toX + AAA;
13729             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13730         } else {
13731             *p++ = '-';
13732         }
13733     } else if(move == backwardMostMove) {
13734         // [HGM] perhaps we should always do it like this, and forget the above?
13735         if(epStatus[move] >= 0) {
13736             *p++ = epStatus[move] + AAA;
13737             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13738         } else {
13739             *p++ = '-';
13740         }
13741     } else {
13742         *p++ = '-';
13743     }
13744     *p++ = ' ';
13745   }
13746   }
13747
13748     /* [HGM] find reversible plies */
13749     {   int i = 0, j=move;
13750
13751         if (appData.debugMode) { int k;
13752             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13753             for(k=backwardMostMove; k<=forwardMostMove; k++)
13754                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13755
13756         }
13757
13758         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13759         if( j == backwardMostMove ) i += initialRulePlies;
13760         sprintf(p, "%d ", i);
13761         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13762     }
13763     /* Fullmove number */
13764     sprintf(p, "%d", (move / 2) + 1);
13765     
13766     return StrSave(buf);
13767 }
13768
13769 Boolean
13770 ParseFEN(board, blackPlaysFirst, fen)
13771     Board board;
13772      int *blackPlaysFirst;
13773      char *fen;
13774 {
13775     int i, j;
13776     char *p;
13777     int emptycount;
13778     ChessSquare piece;
13779
13780     p = fen;
13781
13782     /* [HGM] by default clear Crazyhouse holdings, if present */
13783     if(gameInfo.holdingsWidth) {
13784        for(i=0; i<BOARD_HEIGHT; i++) {
13785            board[i][0]             = EmptySquare; /* black holdings */
13786            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13787            board[i][1]             = (ChessSquare) 0; /* black counts */
13788            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13789        }
13790     }
13791
13792     /* Piece placement data */
13793     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13794         j = 0;
13795         for (;;) {
13796             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13797                 if (*p == '/') p++;
13798                 emptycount = gameInfo.boardWidth - j;
13799                 while (emptycount--)
13800                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13801                 break;
13802 #if(BOARD_SIZE >= 10)
13803             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13804                 p++; emptycount=10;
13805                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13806                 while (emptycount--)
13807                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13808 #endif
13809             } else if (isdigit(*p)) {
13810                 emptycount = *p++ - '0';
13811                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13812                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13813                 while (emptycount--)
13814                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13815             } else if (*p == '+' || isalpha(*p)) {
13816                 if (j >= gameInfo.boardWidth) return FALSE;
13817                 if(*p=='+') {
13818                     piece = CharToPiece(*++p);
13819                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13820                     piece = (ChessSquare) (PROMOTED piece ); p++;
13821                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13822                 } else piece = CharToPiece(*p++);
13823
13824                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13825                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13826                     piece = (ChessSquare) (PROMOTED piece);
13827                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13828                     p++;
13829                 }
13830                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13831             } else {
13832                 return FALSE;
13833             }
13834         }
13835     }
13836     while (*p == '/' || *p == ' ') p++;
13837
13838     /* [HGM] look for Crazyhouse holdings here */
13839     while(*p==' ') p++;
13840     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13841         if(*p == '[') p++;
13842         if(*p == '-' ) *p++; /* empty holdings */ else {
13843             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13844             /* if we would allow FEN reading to set board size, we would   */
13845             /* have to add holdings and shift the board read so far here   */
13846             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13847                 *p++;
13848                 if((int) piece >= (int) BlackPawn ) {
13849                     i = (int)piece - (int)BlackPawn;
13850                     i = PieceToNumber((ChessSquare)i);
13851                     if( i >= gameInfo.holdingsSize ) return FALSE;
13852                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13853                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13854                 } else {
13855                     i = (int)piece - (int)WhitePawn;
13856                     i = PieceToNumber((ChessSquare)i);
13857                     if( i >= gameInfo.holdingsSize ) return FALSE;
13858                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13859                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13860                 }
13861             }
13862         }
13863         if(*p == ']') *p++;
13864     }
13865
13866     while(*p == ' ') p++;
13867
13868     /* Active color */
13869     switch (*p++) {
13870       case 'w':
13871         *blackPlaysFirst = FALSE;
13872         break;
13873       case 'b': 
13874         *blackPlaysFirst = TRUE;
13875         break;
13876       default:
13877         return FALSE;
13878     }
13879
13880     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13881     /* return the extra info in global variiables             */
13882
13883     /* set defaults in case FEN is incomplete */
13884     FENepStatus = EP_UNKNOWN;
13885     for(i=0; i<nrCastlingRights; i++ ) {
13886         FENcastlingRights[i] =
13887             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13888     }   /* assume possible unless obviously impossible */
13889     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13890     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13891     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13892     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13893     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13894     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13895     FENrulePlies = 0;
13896
13897     while(*p==' ') p++;
13898     if(nrCastlingRights) {
13899       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13900           /* castling indicator present, so default becomes no castlings */
13901           for(i=0; i<nrCastlingRights; i++ ) {
13902                  FENcastlingRights[i] = -1;
13903           }
13904       }
13905       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13906              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13907              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13908              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13909         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13910
13911         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13912             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13913             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13914         }
13915         switch(c) {
13916           case'K':
13917               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13918               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13919               FENcastlingRights[2] = whiteKingFile;
13920               break;
13921           case'Q':
13922               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13923               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13924               FENcastlingRights[2] = whiteKingFile;
13925               break;
13926           case'k':
13927               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13928               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13929               FENcastlingRights[5] = blackKingFile;
13930               break;
13931           case'q':
13932               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13933               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13934               FENcastlingRights[5] = blackKingFile;
13935           case '-':
13936               break;
13937           default: /* FRC castlings */
13938               if(c >= 'a') { /* black rights */
13939                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13940                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13941                   if(i == BOARD_RGHT) break;
13942                   FENcastlingRights[5] = i;
13943                   c -= AAA;
13944                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13945                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13946                   if(c > i)
13947                       FENcastlingRights[3] = c;
13948                   else
13949                       FENcastlingRights[4] = c;
13950               } else { /* white rights */
13951                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13952                     if(board[0][i] == WhiteKing) break;
13953                   if(i == BOARD_RGHT) break;
13954                   FENcastlingRights[2] = i;
13955                   c -= AAA - 'a' + 'A';
13956                   if(board[0][c] >= WhiteKing) break;
13957                   if(c > i)
13958                       FENcastlingRights[0] = c;
13959                   else
13960                       FENcastlingRights[1] = c;
13961               }
13962         }
13963       }
13964     if (appData.debugMode) {
13965         fprintf(debugFP, "FEN castling rights:");
13966         for(i=0; i<nrCastlingRights; i++)
13967         fprintf(debugFP, " %d", FENcastlingRights[i]);
13968         fprintf(debugFP, "\n");
13969     }
13970
13971       while(*p==' ') p++;
13972     }
13973
13974     /* read e.p. field in games that know e.p. capture */
13975     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13976        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13977       if(*p=='-') {
13978         p++; FENepStatus = EP_NONE;
13979       } else {
13980          char c = *p++ - AAA;
13981
13982          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13983          if(*p >= '0' && *p <='9') *p++;
13984          FENepStatus = c;
13985       }
13986     }
13987
13988
13989     if(sscanf(p, "%d", &i) == 1) {
13990         FENrulePlies = i; /* 50-move ply counter */
13991         /* (The move number is still ignored)    */
13992     }
13993
13994     return TRUE;
13995 }
13996       
13997 void
13998 EditPositionPasteFEN(char *fen)
13999 {
14000   if (fen != NULL) {
14001     Board initial_position;
14002
14003     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14004       DisplayError(_("Bad FEN position in clipboard"), 0);
14005       return ;
14006     } else {
14007       int savedBlackPlaysFirst = blackPlaysFirst;
14008       EditPositionEvent();
14009       blackPlaysFirst = savedBlackPlaysFirst;
14010       CopyBoard(boards[0], initial_position);
14011           /* [HGM] copy FEN attributes as well */
14012           {   int i;
14013               initialRulePlies = FENrulePlies;
14014               epStatus[0] = FENepStatus;
14015               for( i=0; i<nrCastlingRights; i++ )
14016                   castlingRights[0][i] = FENcastlingRights[i];
14017           }
14018       EditPositionDone();
14019       DisplayBothClocks();
14020       DrawPosition(FALSE, boards[currentMove]);
14021     }
14022   }
14023 }
14024
14025 static char cseq[12] = "\\   ";
14026
14027 Boolean set_cont_sequence(char *new_seq)
14028 {
14029     int len;
14030     Boolean ret;
14031
14032     // handle bad attempts to set the sequence
14033         if (!new_seq)
14034                 return 0; // acceptable error - no debug
14035
14036     len = strlen(new_seq);
14037     ret = (len > 0) && (len < sizeof(cseq));
14038     if (ret)
14039         strcpy(cseq, new_seq);
14040     else if (appData.debugMode)
14041         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14042     return ret;
14043 }
14044
14045 /*
14046     reformat a source message so words don't cross the width boundary.  internal
14047     newlines are not removed.  returns the wrapped size (no null character unless
14048     included in source message).  If dest is NULL, only calculate the size required
14049     for the dest buffer.  lp argument indicats line position upon entry, and it's
14050     passed back upon exit.
14051 */
14052 int wrap(char *dest, char *src, int count, int width, int *lp)
14053 {
14054     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14055
14056     cseq_len = strlen(cseq);
14057     old_line = line = *lp;
14058     ansi = len = clen = 0;
14059
14060     for (i=0; i < count; i++)
14061     {
14062         if (src[i] == '\033')
14063             ansi = 1;
14064
14065         // if we hit the width, back up
14066         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14067         {
14068             // store i & len in case the word is too long
14069             old_i = i, old_len = len;
14070
14071             // find the end of the last word
14072             while (i && src[i] != ' ' && src[i] != '\n')
14073             {
14074                 i--;
14075                 len--;
14076             }
14077
14078             // word too long?  restore i & len before splitting it
14079             if ((old_i-i+clen) >= width)
14080             {
14081                 i = old_i;
14082                 len = old_len;
14083             }
14084
14085             // extra space?
14086             if (i && src[i-1] == ' ')
14087                 len--;
14088
14089             if (src[i] != ' ' && src[i] != '\n')
14090             {
14091                 i--;
14092                 if (len)
14093                     len--;
14094             }
14095
14096             // now append the newline and continuation sequence
14097             if (dest)
14098                 dest[len] = '\n';
14099             len++;
14100             if (dest)
14101                 strncpy(dest+len, cseq, cseq_len);
14102             len += cseq_len;
14103             line = cseq_len;
14104             clen = cseq_len;
14105             continue;
14106         }
14107
14108         if (dest)
14109             dest[len] = src[i];
14110         len++;
14111         if (!ansi)
14112             line++;
14113         if (src[i] == '\n')
14114             line = 0;
14115         if (src[i] == 'm')
14116             ansi = 0;
14117     }
14118     if (dest && appData.debugMode)
14119     {
14120         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14121             count, width, line, len, *lp);
14122         show_bytes(debugFP, src, count);
14123         fprintf(debugFP, "\ndest: ");
14124         show_bytes(debugFP, dest, len);
14125         fprintf(debugFP, "\n");
14126     }
14127     *lp = dest ? line : old_line;
14128
14129     return len;
14130 }