a23d95ea68fdb5e28b0e3b3ec1337e0805462138
[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); // [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        return;
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(FALSE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016    
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\   ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2031
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2034
2035 void
2036 read_from_ics(isr, closure, data, count, error)
2037      InputSourceRef isr;
2038      VOIDSTAR closure;
2039      char *data;
2040      int count;
2041      int error;
2042 {
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2052     
2053     static int started = STARTED_NONE;
2054     static char parse[20000];
2055     static int parse_pos = 0;
2056     static char buf[BUF_SIZE + 1];
2057     static int firstTime = TRUE, intfSet = FALSE;
2058     static ColorClass prevColor = ColorNormal;
2059     static int savingComment = FALSE;
2060     static int cmatch = 0; // continuation sequence match
2061     char *bp;
2062     char str[500];
2063     int i, oldi;
2064     int buf_len;
2065     int next_out;
2066     int tkind;
2067     int backup;    /* [DM] For zippy color lines */
2068     char *p;
2069     char talker[MSG_SIZ]; // [HGM] chat
2070     int channel;
2071
2072     if (appData.debugMode) {
2073       if (!error) {
2074         fprintf(debugFP, "<ICS: ");
2075         show_bytes(debugFP, data, count);
2076         fprintf(debugFP, "\n");
2077       }
2078     }
2079
2080     if (appData.debugMode) { int f = forwardMostMove;
2081         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2083     }
2084     if (count > 0) {
2085         /* If last read ended with a partial line that we couldn't parse,
2086            prepend it to the new read and try again. */
2087         if (leftover_len > 0) {
2088             for (i=0; i<leftover_len; i++)
2089               buf[i] = buf[leftover_start + i];
2090         }
2091
2092     /* copy new characters into the buffer */
2093     bp = buf + leftover_len;
2094     buf_len=leftover_len;
2095     for (i=0; i<count; i++)
2096     {
2097         // ignore these
2098         if (data[i] == '\r')
2099             continue;
2100
2101         // join lines split by ICS?
2102         if (!appData.noJoin)
2103         {
2104             /*
2105                 Joining just consists of finding matches against the
2106                 continuation sequence, and discarding that sequence
2107                 if found instead of copying it.  So, until a match
2108                 fails, there's nothing to do since it might be the
2109                 complete sequence, and thus, something we don't want
2110                 copied.
2111             */
2112             if (data[i] == cont_seq[cmatch])
2113             {
2114                 cmatch++;
2115                 if (cmatch == strlen(cont_seq))
2116                 {
2117                     cmatch = 0; // complete match.  just reset the counter
2118
2119                     /*
2120                         it's possible for the ICS to not include the space
2121                         at the end of the last word, making our [correct]
2122                         join operation fuse two separate words.  the server
2123                         does this when the space occurs at the width setting.
2124                     */
2125                     if (!buf_len || buf[buf_len-1] != ' ')
2126                     {
2127                         *bp++ = ' ';
2128                         buf_len++;
2129                     }
2130                 }
2131                 continue;
2132             }
2133             else if (cmatch)
2134             {
2135                 /*
2136                     match failed, so we have to copy what matched before
2137                     falling through and copying this character.  In reality,
2138                     this will only ever be just the newline character, but
2139                     it doesn't hurt to be precise.
2140                 */
2141                 strncpy(bp, cont_seq, cmatch);
2142                 bp += cmatch;
2143                 buf_len += cmatch;
2144                 cmatch = 0;
2145             }
2146         }
2147
2148         // copy this char
2149         *bp++ = data[i];
2150         buf_len++;
2151     }
2152
2153         buf[buf_len] = NULLCHAR;
2154         next_out = leftover_len;
2155         leftover_start = 0;
2156         
2157         i = 0;
2158         while (i < buf_len) {
2159             /* Deal with part of the TELNET option negotiation
2160                protocol.  We refuse to do anything beyond the
2161                defaults, except that we allow the WILL ECHO option,
2162                which ICS uses to turn off password echoing when we are
2163                directly connected to it.  We reject this option
2164                if localLineEditing mode is on (always on in xboard)
2165                and we are talking to port 23, which might be a real
2166                telnet server that will try to keep WILL ECHO on permanently.
2167              */
2168             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170                 unsigned char option;
2171                 oldi = i;
2172                 switch ((unsigned char) buf[++i]) {
2173                   case TN_WILL:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<WILL ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       case TN_ECHO:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "ECHO ");
2180                         /* Reply only if this is a change, according
2181                            to the protocol rules. */
2182                         if (remoteEchoOption) break;
2183                         if (appData.localLineEditing &&
2184                             atoi(appData.icsPort) == TN_PORT) {
2185                             TelnetRequest(TN_DONT, TN_ECHO);
2186                         } else {
2187                             EchoOff();
2188                             TelnetRequest(TN_DO, TN_ECHO);
2189                             remoteEchoOption = TRUE;
2190                         }
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         /* Whatever this is, we don't want it. */
2196                         TelnetRequest(TN_DONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_WONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (!remoteEchoOption) break;
2210                         EchoOn();
2211                         TelnetRequest(TN_DONT, TN_ECHO);
2212                         remoteEchoOption = FALSE;
2213                         break;
2214                       default:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", (unsigned char) option);
2217                         /* Whatever this is, it must already be turned
2218                            off, because we never agree to turn on
2219                            anything non-default, so according to the
2220                            protocol rules, we don't reply. */
2221                         break;
2222                     }
2223                     break;
2224                   case TN_DO:
2225                     if (appData.debugMode)
2226                       fprintf(debugFP, "\n<DO ");
2227                     switch (option = (unsigned char) buf[++i]) {
2228                       default:
2229                         /* Whatever this is, we refuse to do it. */
2230                         if (appData.debugMode)
2231                           fprintf(debugFP, "%d ", option);
2232                         TelnetRequest(TN_WONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_DONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<DONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         /* Whatever this is, we are already not doing
2244                            it, because we never agree to do anything
2245                            non-default, so according to the protocol
2246                            rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_IAC:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<IAC ");
2253                     /* Doubled IAC; pass it through */
2254                     i--;
2255                     break;
2256                   default:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259                     /* Drop all other telnet commands on the floor */
2260                     break;
2261                 }
2262                 if (oldi > next_out)
2263                   SendToPlayer(&buf[next_out], oldi - next_out);
2264                 if (++i > next_out)
2265                   next_out = i;
2266                 continue;
2267             }
2268                 
2269             /* OK, this at least will *usually* work */
2270             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2271                 loggedOn = TRUE;
2272             }
2273             
2274             if (loggedOn && !intfSet) {
2275                 if (ics_type == ICS_ICC) {
2276                   sprintf(str,
2277                           "/set-quietly interface %s\n/set-quietly style 12\n",
2278                           programVersion);
2279                 } else if (ics_type == ICS_CHESSNET) {
2280                   sprintf(str, "/style 12\n");
2281                 } else {
2282                   strcpy(str, "alias $ @\n$set interface ");
2283                   strcat(str, programVersion);
2284                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 #ifdef WIN32
2286                   strcat(str, "$iset nohighlight 1\n");
2287 #endif
2288                   strcat(str, "$iset lock 1\n$style 12\n");
2289                 }
2290                 SendToICS(str);
2291                 NotifyFrontendLogin();
2292                 intfSet = TRUE;
2293             }
2294
2295             if (started == STARTED_COMMENT) {
2296                 /* Accumulate characters in comment */
2297                 parse[parse_pos++] = buf[i];
2298                 if (buf[i] == '\n') {
2299                     parse[parse_pos] = NULLCHAR;
2300                     if(chattingPartner>=0) {
2301                         char mess[MSG_SIZ];
2302                         sprintf(mess, "%s%s", talker, parse);
2303                         OutputChatMessage(chattingPartner, mess);
2304                         chattingPartner = -1;
2305                     } else
2306                     if(!suppressKibitz) // [HGM] kibitz
2307                         AppendComment(forwardMostMove, StripHighlight(parse));
2308                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309                         int nrDigit = 0, nrAlph = 0, i;
2310                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312                         parse[parse_pos] = NULLCHAR;
2313                         // try to be smart: if it does not look like search info, it should go to
2314                         // ICS interaction window after all, not to engine-output window.
2315                         for(i=0; i<parse_pos; i++) { // count letters and digits
2316                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2318                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2319                         }
2320                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321                             int depth=0; float score;
2322                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324                                 pvInfoList[forwardMostMove-1].depth = depth;
2325                                 pvInfoList[forwardMostMove-1].score = 100*score;
2326                             }
2327                             OutputKibitz(suppressKibitz, parse);
2328                         } else {
2329                             char tmp[MSG_SIZ];
2330                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331                             SendToPlayer(tmp, strlen(tmp));
2332                         }
2333                     }
2334                     started = STARTED_NONE;
2335                 } else {
2336                     /* Don't match patterns against characters in chatter */
2337                     i++;
2338                     continue;
2339                 }
2340             }
2341             if (started == STARTED_CHATTER) {
2342                 if (buf[i] != '\n') {
2343                     /* Don't match patterns against characters in chatter */
2344                     i++;
2345                     continue;
2346                 }
2347                 started = STARTED_NONE;
2348             }
2349
2350             /* Kludge to deal with rcmd protocol */
2351             if (firstTime && looking_at(buf, &i, "\001*")) {
2352                 DisplayFatalError(&buf[1], 0, 1);
2353                 continue;
2354             } else {
2355                 firstTime = FALSE;
2356             }
2357
2358             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2359                 ics_type = ICS_ICC;
2360                 ics_prefix = "/";
2361                 if (appData.debugMode)
2362                   fprintf(debugFP, "ics_type %d\n", ics_type);
2363                 continue;
2364             }
2365             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366                 ics_type = ICS_FICS;
2367                 ics_prefix = "$";
2368                 if (appData.debugMode)
2369                   fprintf(debugFP, "ics_type %d\n", ics_type);
2370                 continue;
2371             }
2372             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373                 ics_type = ICS_CHESSNET;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379
2380             if (!loggedOn &&
2381                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2383                  looking_at(buf, &i, "will be \"*\""))) {
2384               strcpy(ics_handle, star_match[0]);
2385               continue;
2386             }
2387
2388             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389               char buf[MSG_SIZ];
2390               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391               DisplayIcsInteractionTitle(buf);
2392               have_set_title = TRUE;
2393             }
2394
2395             /* skip finger notes */
2396             if (started == STARTED_NONE &&
2397                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398                  (buf[i] == '1' && buf[i+1] == '0')) &&
2399                 buf[i+2] == ':' && buf[i+3] == ' ') {
2400               started = STARTED_CHATTER;
2401               i += 3;
2402               continue;
2403             }
2404
2405             /* skip formula vars */
2406             if (started == STARTED_NONE &&
2407                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408               started = STARTED_CHATTER;
2409               i += 3;
2410               continue;
2411             }
2412
2413             oldi = i;
2414             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415             if (appData.autoKibitz && started == STARTED_NONE && 
2416                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2417                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418                 if(looking_at(buf, &i, "* kibitzes: ") &&
2419                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2420                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2421                         suppressKibitz = TRUE;
2422                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423                                 && (gameMode == IcsPlayingWhite)) ||
2424                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2426                             started = STARTED_CHATTER; // own kibitz we simply discard
2427                         else {
2428                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429                             parse_pos = 0; parse[0] = NULLCHAR;
2430                             savingComment = TRUE;
2431                             suppressKibitz = gameMode != IcsObserving ? 2 :
2432                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2433                         } 
2434                         continue;
2435                 } else
2436                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437                     started = STARTED_CHATTER;
2438                     suppressKibitz = TRUE;
2439                 }
2440             } // [HGM] kibitz: end of patch
2441
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443
2444             // [HGM] chat: intercept tells by users for which we have an open chat window
2445             channel = -1;
2446             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2447                                            looking_at(buf, &i, "* whispers:") ||
2448                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450                 int p;
2451                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452                 chattingPartner = -1;
2453
2454                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455                 for(p=0; p<MAX_CHAT; p++) {
2456                     if(channel == atoi(chatPartner[p])) {
2457                     talker[0] = '['; strcat(talker, "]");
2458                     chattingPartner = p; break;
2459                     }
2460                 } else
2461                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462                 for(p=0; p<MAX_CHAT; p++) {
2463                     if(!strcmp("WHISPER", chatPartner[p])) {
2464                         talker[0] = '['; strcat(talker, "]");
2465                         chattingPartner = p; break;
2466                     }
2467                 }
2468                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470                     talker[0] = 0;
2471                     chattingPartner = p; break;
2472                 }
2473                 if(chattingPartner<0) i = oldi; else {
2474                     started = STARTED_COMMENT;
2475                     parse_pos = 0; parse[0] = NULLCHAR;
2476                     savingComment = TRUE;
2477                     suppressKibitz = TRUE;
2478                 }
2479             } // [HGM] chat: end of patch
2480
2481             if (appData.zippyTalk || appData.zippyPlay) {
2482                 /* [DM] Backup address for color zippy lines */
2483                 backup = i;
2484 #if ZIPPY
2485        #ifdef WIN32
2486                if (loggedOn == TRUE)
2487                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489        #else
2490                 if (ZippyControl(buf, &i) ||
2491                     ZippyConverse(buf, &i) ||
2492                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493                       loggedOn = TRUE;
2494                       if (!appData.colorize) continue;
2495                 }
2496        #endif
2497 #endif
2498             } // [DM] 'else { ' deleted
2499                 if (
2500                     /* Regular tells and says */
2501                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2503                     looking_at(buf, &i, "* says: ") ||
2504                     /* Don't color "message" or "messages" output */
2505                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506                     looking_at(buf, &i, "*. * at *:*: ") ||
2507                     looking_at(buf, &i, "--* (*:*): ") ||
2508                     /* Message notifications (same color as tells) */
2509                     looking_at(buf, &i, "* has left a message ") ||
2510                     looking_at(buf, &i, "* just sent you a message:\n") ||
2511                     /* Whispers and kibitzes */
2512                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513                     looking_at(buf, &i, "* kibitzes: ") ||
2514                     /* Channel tells */
2515                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516
2517                   if (tkind == 1 && strchr(star_match[0], ':')) {
2518                       /* Avoid "tells you:" spoofs in channels */
2519                      tkind = 3;
2520                   }
2521                   if (star_match[0][0] == NULLCHAR ||
2522                       strchr(star_match[0], ' ') ||
2523                       (tkind == 3 && strchr(star_match[1], ' '))) {
2524                     /* Reject bogus matches */
2525                     i = oldi;
2526                   } else {
2527                     if (appData.colorize) {
2528                       if (oldi > next_out) {
2529                         SendToPlayer(&buf[next_out], oldi - next_out);
2530                         next_out = oldi;
2531                       }
2532                       switch (tkind) {
2533                       case 1:
2534                         Colorize(ColorTell, FALSE);
2535                         curColor = ColorTell;
2536                         break;
2537                       case 2:
2538                         Colorize(ColorKibitz, FALSE);
2539                         curColor = ColorKibitz;
2540                         break;
2541                       case 3:
2542                         p = strrchr(star_match[1], '(');
2543                         if (p == NULL) {
2544                           p = star_match[1];
2545                         } else {
2546                           p++;
2547                         }
2548                         if (atoi(p) == 1) {
2549                           Colorize(ColorChannel1, FALSE);
2550                           curColor = ColorChannel1;
2551                         } else {
2552                           Colorize(ColorChannel, FALSE);
2553                           curColor = ColorChannel;
2554                         }
2555                         break;
2556                       case 5:
2557                         curColor = ColorNormal;
2558                         break;
2559                       }
2560                     }
2561                     if (started == STARTED_NONE && appData.autoComment &&
2562                         (gameMode == IcsObserving ||
2563                          gameMode == IcsPlayingWhite ||
2564                          gameMode == IcsPlayingBlack)) {
2565                       parse_pos = i - oldi;
2566                       memcpy(parse, &buf[oldi], parse_pos);
2567                       parse[parse_pos] = NULLCHAR;
2568                       started = STARTED_COMMENT;
2569                       savingComment = TRUE;
2570                     } else {
2571                       started = STARTED_CHATTER;
2572                       savingComment = FALSE;
2573                     }
2574                     loggedOn = TRUE;
2575                     continue;
2576                   }
2577                 }
2578
2579                 if (looking_at(buf, &i, "* s-shouts: ") ||
2580                     looking_at(buf, &i, "* c-shouts: ")) {
2581                     if (appData.colorize) {
2582                         if (oldi > next_out) {
2583                             SendToPlayer(&buf[next_out], oldi - next_out);
2584                             next_out = oldi;
2585                         }
2586                         Colorize(ColorSShout, FALSE);
2587                         curColor = ColorSShout;
2588                     }
2589                     loggedOn = TRUE;
2590                     started = STARTED_CHATTER;
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "--->")) {
2595                     loggedOn = TRUE;
2596                     continue;
2597                 }
2598
2599                 if (looking_at(buf, &i, "* shouts: ") ||
2600                     looking_at(buf, &i, "--> ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorShout, FALSE);
2607                         curColor = ColorShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at( buf, &i, "Challenge:")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorChallenge, FALSE);
2621                         curColor = ColorChallenge;
2622                     }
2623                     loggedOn = TRUE;
2624                     continue;
2625                 }
2626
2627                 if (looking_at(buf, &i, "* offers you") ||
2628                     looking_at(buf, &i, "* offers to be") ||
2629                     looking_at(buf, &i, "* would like to") ||
2630                     looking_at(buf, &i, "* requests to") ||
2631                     looking_at(buf, &i, "Your opponent offers") ||
2632                     looking_at(buf, &i, "Your opponent requests")) {
2633
2634                     if (appData.colorize) {
2635                         if (oldi > next_out) {
2636                             SendToPlayer(&buf[next_out], oldi - next_out);
2637                             next_out = oldi;
2638                         }
2639                         Colorize(ColorRequest, FALSE);
2640                         curColor = ColorRequest;
2641                     }
2642                     continue;
2643                 }
2644
2645                 if (looking_at(buf, &i, "* (*) seeking")) {
2646                     if (appData.colorize) {
2647                         if (oldi > next_out) {
2648                             SendToPlayer(&buf[next_out], oldi - next_out);
2649                             next_out = oldi;
2650                         }
2651                         Colorize(ColorSeek, FALSE);
2652                         curColor = ColorSeek;
2653                     }
2654                     continue;
2655             }
2656
2657             if (looking_at(buf, &i, "\\   ")) {
2658                 if (prevColor != ColorNormal) {
2659                     if (oldi > next_out) {
2660                         SendToPlayer(&buf[next_out], oldi - next_out);
2661                         next_out = oldi;
2662                     }
2663                     Colorize(prevColor, TRUE);
2664                     curColor = prevColor;
2665                 }
2666                 if (savingComment) {
2667                     parse_pos = i - oldi;
2668                     memcpy(parse, &buf[oldi], parse_pos);
2669                     parse[parse_pos] = NULLCHAR;
2670                     started = STARTED_COMMENT;
2671                 } else {
2672                     started = STARTED_CHATTER;
2673                 }
2674                 continue;
2675             }
2676
2677             if (looking_at(buf, &i, "Black Strength :") ||
2678                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679                 looking_at(buf, &i, "<10>") ||
2680                 looking_at(buf, &i, "#@#")) {
2681                 /* Wrong board style */
2682                 loggedOn = TRUE;
2683                 SendToICS(ics_prefix);
2684                 SendToICS("set style 12\n");
2685                 SendToICS(ics_prefix);
2686                 SendToICS("refresh\n");
2687                 continue;
2688             }
2689             
2690             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691                 ICSInitScript();
2692                 have_sent_ICS_logon = 1;
2693                 continue;
2694             }
2695               
2696             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2697                 (looking_at(buf, &i, "\n<12> ") ||
2698                  looking_at(buf, &i, "<12> "))) {
2699                 loggedOn = TRUE;
2700                 if (oldi > next_out) {
2701                     SendToPlayer(&buf[next_out], oldi - next_out);
2702                 }
2703                 next_out = i;
2704                 started = STARTED_BOARD;
2705                 parse_pos = 0;
2706                 continue;
2707             }
2708
2709             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710                 looking_at(buf, &i, "<b1> ")) {
2711                 if (oldi > next_out) {
2712                     SendToPlayer(&buf[next_out], oldi - next_out);
2713                 }
2714                 next_out = i;
2715                 started = STARTED_HOLDINGS;
2716                 parse_pos = 0;
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721                 loggedOn = TRUE;
2722                 /* Header for a move list -- first line */
2723
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     switch (gameMode) {
2727                       case IcsIdle:
2728                       case BeginningOfGame:
2729                         /* User typed "moves" or "oldmoves" while we
2730                            were idle.  Pretend we asked for these
2731                            moves and soak them up so user can step
2732                            through them and/or save them.
2733                            */
2734                         Reset(FALSE, TRUE);
2735                         gameMode = IcsObserving;
2736                         ModeHighlight();
2737                         ics_gamenum = -1;
2738                         ics_getting_history = H_GOT_UNREQ_HEADER;
2739                         break;
2740                       case EditGame: /*?*/
2741                       case EditPosition: /*?*/
2742                         /* Should above feature work in these modes too? */
2743                         /* For now it doesn't */
2744                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2745                         break;
2746                       default:
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                     }
2750                     break;
2751                   case H_REQUESTED:
2752                     /* Is this the right one? */
2753                     if (gameInfo.white && gameInfo.black &&
2754                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2755                         strcmp(gameInfo.black, star_match[2]) == 0) {
2756                         /* All is well */
2757                         ics_getting_history = H_GOT_REQ_HEADER;
2758                     }
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                   case H_GOT_UNREQ_HEADER:
2762                   case H_GOT_UNWANTED_HEADER:
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: two headers"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                 }
2769
2770                 /* Save player ratings into gameInfo if needed */
2771                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773                     (gameInfo.whiteRating == -1 ||
2774                      gameInfo.blackRating == -1)) {
2775
2776                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2777                     gameInfo.blackRating = string_to_rating(star_match[3]);
2778                     if (appData.debugMode)
2779                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2780                               gameInfo.whiteRating, gameInfo.blackRating);
2781                 }
2782                 continue;
2783             }
2784
2785             if (looking_at(buf, &i,
2786               "* * match, initial time: * minute*, increment: * second")) {
2787                 /* Header for a move list -- second line */
2788                 /* Initial board will follow if this is a wild game */
2789                 if (gameInfo.event != NULL) free(gameInfo.event);
2790                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791                 gameInfo.event = StrSave(str);
2792                 /* [HGM] we switched variant. Translate boards if needed. */
2793                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i, "Move  ")) {
2798                 /* Beginning of a move list */
2799                 switch (ics_getting_history) {
2800                   case H_FALSE:
2801                     /* Normally should not happen */
2802                     /* Maybe user hit reset while we were parsing */
2803                     break;
2804                   case H_REQUESTED:
2805                     /* Happens if we are ignoring a move list that is not
2806                      * the one we just requested.  Common if the user
2807                      * tries to observe two games without turning off
2808                      * getMoveList */
2809                     break;
2810                   case H_GETTING_MOVES:
2811                     /* Should not happen */
2812                     DisplayError(_("Error gathering move list: nested"), 0);
2813                     ics_getting_history = H_FALSE;
2814                     break;
2815                   case H_GOT_REQ_HEADER:
2816                     ics_getting_history = H_GETTING_MOVES;
2817                     started = STARTED_MOVES;
2818                     parse_pos = 0;
2819                     if (oldi > next_out) {
2820                         SendToPlayer(&buf[next_out], oldi - next_out);
2821                     }
2822                     break;
2823                   case H_GOT_UNREQ_HEADER:
2824                     ics_getting_history = H_GETTING_MOVES;
2825                     started = STARTED_MOVES_NOHIDE;
2826                     parse_pos = 0;
2827                     break;
2828                   case H_GOT_UNWANTED_HEADER:
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                 }
2832                 continue;
2833             }                           
2834             
2835             if (looking_at(buf, &i, "% ") ||
2836                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838                 savingComment = FALSE;
2839                 switch (started) {
2840                   case STARTED_MOVES:
2841                   case STARTED_MOVES_NOHIDE:
2842                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843                     parse[parse_pos + i - oldi] = NULLCHAR;
2844                     ParseGameHistory(parse);
2845 #if ZIPPY
2846                     if (appData.zippyPlay && first.initDone) {
2847                         FeedMovesToProgram(&first, forwardMostMove);
2848                         if (gameMode == IcsPlayingWhite) {
2849                             if (WhiteOnMove(forwardMostMove)) {
2850                                 if (first.sendTime) {
2851                                   if (first.useColors) {
2852                                     SendToProgram("black\n", &first); 
2853                                   }
2854                                   SendTimeRemaining(&first, TRUE);
2855                                 }
2856                                 if (first.useColors) {
2857                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858                                 }
2859                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860                                 first.maybeThinking = TRUE;
2861                             } else {
2862                                 if (first.usePlayother) {
2863                                   if (first.sendTime) {
2864                                     SendTimeRemaining(&first, TRUE);
2865                                   }
2866                                   SendToProgram("playother\n", &first);
2867                                   firstMove = FALSE;
2868                                 } else {
2869                                   firstMove = TRUE;
2870                                 }
2871                             }
2872                         } else if (gameMode == IcsPlayingBlack) {
2873                             if (!WhiteOnMove(forwardMostMove)) {
2874                                 if (first.sendTime) {
2875                                   if (first.useColors) {
2876                                     SendToProgram("white\n", &first);
2877                                   }
2878                                   SendTimeRemaining(&first, FALSE);
2879                                 }
2880                                 if (first.useColors) {
2881                                   SendToProgram("black\n", &first);
2882                                 }
2883                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884                                 first.maybeThinking = TRUE;
2885                             } else {
2886                                 if (first.usePlayother) {
2887                                   if (first.sendTime) {
2888                                     SendTimeRemaining(&first, FALSE);
2889                                   }
2890                                   SendToProgram("playother\n", &first);
2891                                   firstMove = FALSE;
2892                                 } else {
2893                                   firstMove = TRUE;
2894                                 }
2895                             }
2896                         }                       
2897                     }
2898 #endif
2899                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2900                         /* Moves came from oldmoves or moves command
2901                            while we weren't doing anything else.
2902                            */
2903                         currentMove = forwardMostMove;
2904                         ClearHighlights();/*!!could figure this out*/
2905                         flipView = appData.flipView;
2906                         DrawPosition(FALSE, boards[currentMove]);
2907                         DisplayBothClocks();
2908                         sprintf(str, "%s vs. %s",
2909                                 gameInfo.white, gameInfo.black);
2910                         DisplayTitle(str);
2911                         gameMode = IcsIdle;
2912                     } else {
2913                         /* Moves were history of an active game */
2914                         if (gameInfo.resultDetails != NULL) {
2915                             free(gameInfo.resultDetails);
2916                             gameInfo.resultDetails = NULL;
2917                         }
2918                     }
2919                     HistorySet(parseList, backwardMostMove,
2920                                forwardMostMove, currentMove-1);
2921                     DisplayMove(currentMove - 1);
2922                     if (started == STARTED_MOVES) next_out = i;
2923                     started = STARTED_NONE;
2924                     ics_getting_history = H_FALSE;
2925                     break;
2926
2927                   case STARTED_OBSERVE:
2928                     started = STARTED_NONE;
2929                     SendToICS(ics_prefix);
2930                     SendToICS("refresh\n");
2931                     break;
2932
2933                   default:
2934                     break;
2935                 }
2936                 if(bookHit) { // [HGM] book: simulate book reply
2937                     static char bookMove[MSG_SIZ]; // a bit generous?
2938
2939                     programStats.nodes = programStats.depth = programStats.time = 
2940                     programStats.score = programStats.got_only_move = 0;
2941                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942
2943                     strcpy(bookMove, "move ");
2944                     strcat(bookMove, bookHit);
2945                     HandleMachineMove(bookMove, &first);
2946                 }
2947                 continue;
2948             }
2949             
2950             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951                  started == STARTED_HOLDINGS ||
2952                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953                 /* Accumulate characters in move list or board */
2954                 parse[parse_pos++] = buf[i];
2955             }
2956             
2957             /* Start of game messages.  Mostly we detect start of game
2958                when the first board image arrives.  On some versions
2959                of the ICS, though, we need to do a "refresh" after starting
2960                to observe in order to get the current board right away. */
2961             if (looking_at(buf, &i, "Adding game * to observation list")) {
2962                 started = STARTED_OBSERVE;
2963                 continue;
2964             }
2965
2966             /* Handle auto-observe */
2967             if (appData.autoObserve &&
2968                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970                 char *player;
2971                 /* Choose the player that was highlighted, if any. */
2972                 if (star_match[0][0] == '\033' ||
2973                     star_match[1][0] != '\033') {
2974                     player = star_match[0];
2975                 } else {
2976                     player = star_match[2];
2977                 }
2978                 sprintf(str, "%sobserve %s\n",
2979                         ics_prefix, StripHighlightAndTitle(player));
2980                 SendToICS(str);
2981
2982                 /* Save ratings from notify string */
2983                 strcpy(player1Name, star_match[0]);
2984                 player1Rating = string_to_rating(star_match[1]);
2985                 strcpy(player2Name, star_match[2]);
2986                 player2Rating = string_to_rating(star_match[3]);
2987
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, 
2990                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2991                           player1Name, player1Rating,
2992                           player2Name, player2Rating);
2993
2994                 continue;
2995             }
2996
2997             /* Deal with automatic examine mode after a game,
2998                and with IcsObserving -> IcsExamining transition */
2999             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000                 looking_at(buf, &i, "has made you an examiner of game *")) {
3001
3002                 int gamenum = atoi(star_match[0]);
3003                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004                     gamenum == ics_gamenum) {
3005                     /* We were already playing or observing this game;
3006                        no need to refetch history */
3007                     gameMode = IcsExamining;
3008                     if (pausing) {
3009                         pauseExamForwardMostMove = forwardMostMove;
3010                     } else if (currentMove < forwardMostMove) {
3011                         ForwardInner(forwardMostMove);
3012                     }
3013                 } else {
3014                     /* I don't think this case really can happen */
3015                     SendToICS(ics_prefix);
3016                     SendToICS("refresh\n");
3017                 }
3018                 continue;
3019             }    
3020             
3021             /* Error messages */
3022 //          if (ics_user_moved) {
3023             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024                 if (looking_at(buf, &i, "Illegal move") ||
3025                     looking_at(buf, &i, "Not a legal move") ||
3026                     looking_at(buf, &i, "Your king is in check") ||
3027                     looking_at(buf, &i, "It isn't your turn") ||
3028                     looking_at(buf, &i, "It is not your move")) {
3029                     /* Illegal move */
3030                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031                         currentMove = --forwardMostMove;
3032                         DisplayMove(currentMove - 1); /* before DMError */
3033                         DrawPosition(FALSE, boards[currentMove]);
3034                         SwitchClocks();
3035                         DisplayBothClocks();
3036                     }
3037                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038                     ics_user_moved = 0;
3039                     continue;
3040                 }
3041             }
3042
3043             if (looking_at(buf, &i, "still have time") ||
3044                 looking_at(buf, &i, "not out of time") ||
3045                 looking_at(buf, &i, "either player is out of time") ||
3046                 looking_at(buf, &i, "has timeseal; checking")) {
3047                 /* We must have called his flag a little too soon */
3048                 whiteFlag = blackFlag = FALSE;
3049                 continue;
3050             }
3051
3052             if (looking_at(buf, &i, "added * seconds to") ||
3053                 looking_at(buf, &i, "seconds were added to")) {
3054                 /* Update the clocks */
3055                 SendToICS(ics_prefix);
3056                 SendToICS("refresh\n");
3057                 continue;
3058             }
3059
3060             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061                 ics_clock_paused = TRUE;
3062                 StopClocks();
3063                 continue;
3064             }
3065
3066             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067                 ics_clock_paused = FALSE;
3068                 StartClocks();
3069                 continue;
3070             }
3071
3072             /* Grab player ratings from the Creating: message.
3073                Note we have to check for the special case when
3074                the ICS inserts things like [white] or [black]. */
3075             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077                 /* star_matches:
3078                    0    player 1 name (not necessarily white)
3079                    1    player 1 rating
3080                    2    empty, white, or black (IGNORED)
3081                    3    player 2 name (not necessarily black)
3082                    4    player 2 rating
3083                    
3084                    The names/ratings are sorted out when the game
3085                    actually starts (below).
3086                 */
3087                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088                 player1Rating = string_to_rating(star_match[1]);
3089                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090                 player2Rating = string_to_rating(star_match[4]);
3091
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, 
3094                           "Ratings from 'Creating:' %s %d, %s %d\n",
3095                           player1Name, player1Rating,
3096                           player2Name, player2Rating);
3097
3098                 continue;
3099             }
3100             
3101             /* Improved generic start/end-of-game messages */
3102             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104                 /* If tkind == 0: */
3105                 /* star_match[0] is the game number */
3106                 /*           [1] is the white player's name */
3107                 /*           [2] is the black player's name */
3108                 /* For end-of-game: */
3109                 /*           [3] is the reason for the game end */
3110                 /*           [4] is a PGN end game-token, preceded by " " */
3111                 /* For start-of-game: */
3112                 /*           [3] begins with "Creating" or "Continuing" */
3113                 /*           [4] is " *" or empty (don't care). */
3114                 int gamenum = atoi(star_match[0]);
3115                 char *whitename, *blackname, *why, *endtoken;
3116                 ChessMove endtype = (ChessMove) 0;
3117
3118                 if (tkind == 0) {
3119                   whitename = star_match[1];
3120                   blackname = star_match[2];
3121                   why = star_match[3];
3122                   endtoken = star_match[4];
3123                 } else {
3124                   whitename = star_match[1];
3125                   blackname = star_match[3];
3126                   why = star_match[5];
3127                   endtoken = star_match[6];
3128                 }
3129
3130                 /* Game start messages */
3131                 if (strncmp(why, "Creating ", 9) == 0 ||
3132                     strncmp(why, "Continuing ", 11) == 0) {
3133                     gs_gamenum = gamenum;
3134                     strcpy(gs_kind, strchr(why, ' ') + 1);
3135 #if ZIPPY
3136                     if (appData.zippyPlay) {
3137                         ZippyGameStart(whitename, blackname);
3138                     }
3139 #endif /*ZIPPY*/
3140                     continue;
3141                 }
3142
3143                 /* Game end messages */
3144                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145                     ics_gamenum != gamenum) {
3146                     continue;
3147                 }
3148                 while (endtoken[0] == ' ') endtoken++;
3149                 switch (endtoken[0]) {
3150                   case '*':
3151                   default:
3152                     endtype = GameUnfinished;
3153                     break;
3154                   case '0':
3155                     endtype = BlackWins;
3156                     break;
3157                   case '1':
3158                     if (endtoken[1] == '/')
3159                       endtype = GameIsDrawn;
3160                     else
3161                       endtype = WhiteWins;
3162                     break;
3163                 }
3164                 GameEnds(endtype, why, GE_ICS);
3165 #if ZIPPY
3166                 if (appData.zippyPlay && first.initDone) {
3167                     ZippyGameEnd(endtype, why);
3168                     if (first.pr == NULL) {
3169                       /* Start the next process early so that we'll
3170                          be ready for the next challenge */
3171                       StartChessProgram(&first);
3172                     }
3173                     /* Send "new" early, in case this command takes
3174                        a long time to finish, so that we'll be ready
3175                        for the next challenge. */
3176                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177                     Reset(TRUE, TRUE);
3178                 }
3179 #endif /*ZIPPY*/
3180                 continue;
3181             }
3182
3183             if (looking_at(buf, &i, "Removing game * from observation") ||
3184                 looking_at(buf, &i, "no longer observing game *") ||
3185                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186                 if (gameMode == IcsObserving &&
3187                     atoi(star_match[0]) == ics_gamenum)
3188                   {
3189                       /* icsEngineAnalyze */
3190                       if (appData.icsEngineAnalyze) {
3191                             ExitAnalyzeMode();
3192                             ModeHighlight();
3193                       }
3194                       StopClocks();
3195                       gameMode = IcsIdle;
3196                       ics_gamenum = -1;
3197                       ics_user_moved = FALSE;
3198                   }
3199                 continue;
3200             }
3201
3202             if (looking_at(buf, &i, "no longer examining game *")) {
3203                 if (gameMode == IcsExamining &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             /* Advance leftover_start past any newlines we find,
3214                so only partial lines can get reparsed */
3215             if (looking_at(buf, &i, "\n")) {
3216                 prevColor = curColor;
3217                 if (curColor != ColorNormal) {
3218                     if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                     }
3222                     Colorize(ColorNormal, FALSE);
3223                     curColor = ColorNormal;
3224                 }
3225                 if (started == STARTED_BOARD) {
3226                     started = STARTED_NONE;
3227                     parse[parse_pos] = NULLCHAR;
3228                     ParseBoard12(parse);
3229                     ics_user_moved = 0;
3230
3231                     /* Send premove here */
3232                     if (appData.premove) {
3233                       char str[MSG_SIZ];
3234                       if (currentMove == 0 &&
3235                           gameMode == IcsPlayingWhite &&
3236                           appData.premoveWhite) {
3237                         sprintf(str, "%s%s\n", ics_prefix,
3238                                 appData.premoveWhiteText);
3239                         if (appData.debugMode)
3240                           fprintf(debugFP, "Sending premove:\n");
3241                         SendToICS(str);
3242                       } else if (currentMove == 1 &&
3243                                  gameMode == IcsPlayingBlack &&
3244                                  appData.premoveBlack) {
3245                         sprintf(str, "%s%s\n", ics_prefix,
3246                                 appData.premoveBlackText);
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                         SendToICS(str);
3250                       } else if (gotPremove) {
3251                         gotPremove = 0;
3252                         ClearPremoveHighlights();
3253                         if (appData.debugMode)
3254                           fprintf(debugFP, "Sending premove:\n");
3255                           UserMoveEvent(premoveFromX, premoveFromY, 
3256                                         premoveToX, premoveToY, 
3257                                         premovePromoChar);
3258                       }
3259                     }
3260
3261                     /* Usually suppress following prompt */
3262                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263                         if (looking_at(buf, &i, "*% ")) {
3264                             savingComment = FALSE;
3265                         }
3266                     }
3267                     next_out = i;
3268                 } else if (started == STARTED_HOLDINGS) {
3269                     int gamenum;
3270                     char new_piece[MSG_SIZ];
3271                     started = STARTED_NONE;
3272                     parse[parse_pos] = NULLCHAR;
3273                     if (appData.debugMode)
3274                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275                                                         parse, currentMove);
3276                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277                         gamenum == ics_gamenum) {
3278                         if (gameInfo.variant == VariantNormal) {
3279                           /* [HGM] We seem to switch variant during a game!
3280                            * Presumably no holdings were displayed, so we have
3281                            * to move the position two files to the right to
3282                            * create room for them!
3283                            */
3284                           VariantClass newVariant;
3285                           switch(gameInfo.boardWidth) { // base guess on board width
3286                                 case 9:  newVariant = VariantShogi; break;
3287                                 case 10: newVariant = VariantGreat; break;
3288                                 default: newVariant = VariantCrazyhouse; break;
3289                           }
3290                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3291                           /* Get a move list just to see the header, which
3292                              will tell us whether this is really bug or zh */
3293                           if (ics_getting_history == H_FALSE) {
3294                             ics_getting_history = H_REQUESTED;
3295                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3296                             SendToICS(str);
3297                           }
3298                         }
3299                         new_piece[0] = NULLCHAR;
3300                         sscanf(parse, "game %d white [%s black [%s <- %s",
3301                                &gamenum, white_holding, black_holding,
3302                                new_piece);
3303                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3304                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3305                         /* [HGM] copy holdings to board holdings area */
3306                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3307                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3308 #if ZIPPY
3309                         if (appData.zippyPlay && first.initDone) {
3310                             ZippyHoldings(white_holding, black_holding,
3311                                           new_piece);
3312                         }
3313 #endif /*ZIPPY*/
3314                         if (tinyLayout || smallLayout) {
3315                             char wh[16], bh[16];
3316                             PackHolding(wh, white_holding);
3317                             PackHolding(bh, black_holding);
3318                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3319                                     gameInfo.white, gameInfo.black);
3320                         } else {
3321                             sprintf(str, "%s [%s] vs. %s [%s]",
3322                                     gameInfo.white, white_holding,
3323                                     gameInfo.black, black_holding);
3324                         }
3325
3326                         DrawPosition(FALSE, boards[currentMove]);
3327                         DisplayTitle(str);
3328                     }
3329                     /* Suppress following prompt */
3330                     if (looking_at(buf, &i, "*% ")) {
3331                         savingComment = FALSE;
3332                     }
3333                     next_out = i;
3334                 }
3335                 continue;
3336             }
3337
3338             i++;                /* skip unparsed character and loop back */
3339         }
3340         
3341         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3342             started != STARTED_HOLDINGS && i > next_out) {
3343             SendToPlayer(&buf[next_out], i - next_out);
3344             next_out = i;
3345         }
3346         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3347         
3348         leftover_len = buf_len - leftover_start;
3349         /* if buffer ends with something we couldn't parse,
3350            reparse it after appending the next read */
3351         
3352     } else if (count == 0) {
3353         RemoveInputSource(isr);
3354         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3355     } else {
3356         DisplayFatalError(_("Error reading from ICS"), error, 1);
3357     }
3358 }
3359
3360
3361 /* Board style 12 looks like this:
3362    
3363    <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
3364    
3365  * The "<12> " is stripped before it gets to this routine.  The two
3366  * trailing 0's (flip state and clock ticking) are later addition, and
3367  * some chess servers may not have them, or may have only the first.
3368  * Additional trailing fields may be added in the future.  
3369  */
3370
3371 #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"
3372
3373 #define RELATION_OBSERVING_PLAYED    0
3374 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3375 #define RELATION_PLAYING_MYMOVE      1
3376 #define RELATION_PLAYING_NOTMYMOVE  -1
3377 #define RELATION_EXAMINING           2
3378 #define RELATION_ISOLATED_BOARD     -3
3379 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3380
3381 void
3382 ParseBoard12(string)
3383      char *string;
3384
3385     GameMode newGameMode;
3386     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3387     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3388     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3389     char to_play, board_chars[200];
3390     char move_str[500], str[500], elapsed_time[500];
3391     char black[32], white[32];
3392     Board board;
3393     int prevMove = currentMove;
3394     int ticking = 2;
3395     ChessMove moveType;
3396     int fromX, fromY, toX, toY;
3397     char promoChar;
3398     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3399     char *bookHit = NULL; // [HGM] book
3400     Boolean weird = FALSE;
3401
3402     fromX = fromY = toX = toY = -1;
3403     
3404     newGame = FALSE;
3405
3406     if (appData.debugMode)
3407       fprintf(debugFP, _("Parsing board: %s\n"), string);
3408
3409     move_str[0] = NULLCHAR;
3410     elapsed_time[0] = NULLCHAR;
3411     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3412         int  i = 0, j;
3413         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3414             if(string[i] == ' ') { ranks++; files = 0; }
3415             else files++;
3416             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3417             i++;
3418         }
3419         for(j = 0; j <i; j++) board_chars[j] = string[j];
3420         board_chars[i] = '\0';
3421         string += i + 1;
3422     }
3423     n = sscanf(string, PATTERN, &to_play, &double_push,
3424                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3425                &gamenum, white, black, &relation, &basetime, &increment,
3426                &white_stren, &black_stren, &white_time, &black_time,
3427                &moveNum, str, elapsed_time, move_str, &ics_flip,
3428                &ticking);
3429
3430    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3431                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3432      /* [HGM] We seem to switch variant during a game!
3433       * Try to guess new variant from board size
3434       */
3435           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3436           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3437           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3438           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3439           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3440           if(!weird) newVariant = VariantNormal;
3441           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3442           /* Get a move list just to see the header, which
3443              will tell us whether this is really bug or zh */
3444           if (ics_getting_history == H_FALSE) {
3445             ics_getting_history = H_REQUESTED;
3446             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3447             SendToICS(str);
3448           }
3449     }
3450
3451     if (n < 21) {
3452         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3453         DisplayError(str, 0);
3454         return;
3455     }
3456
3457     /* Convert the move number to internal form */
3458     moveNum = (moveNum - 1) * 2;
3459     if (to_play == 'B') moveNum++;
3460     if (moveNum >= MAX_MOVES) {
3461       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3462                         0, 1);
3463       return;
3464     }
3465     
3466     switch (relation) {
3467       case RELATION_OBSERVING_PLAYED:
3468       case RELATION_OBSERVING_STATIC:
3469         if (gamenum == -1) {
3470             /* Old ICC buglet */
3471             relation = RELATION_OBSERVING_STATIC;
3472         }
3473         newGameMode = IcsObserving;
3474         break;
3475       case RELATION_PLAYING_MYMOVE:
3476       case RELATION_PLAYING_NOTMYMOVE:
3477         newGameMode =
3478           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3479             IcsPlayingWhite : IcsPlayingBlack;
3480         break;
3481       case RELATION_EXAMINING:
3482         newGameMode = IcsExamining;
3483         break;
3484       case RELATION_ISOLATED_BOARD:
3485       default:
3486         /* Just display this board.  If user was doing something else,
3487            we will forget about it until the next board comes. */ 
3488         newGameMode = IcsIdle;
3489         break;
3490       case RELATION_STARTING_POSITION:
3491         newGameMode = gameMode;
3492         break;
3493     }
3494     
3495     /* Modify behavior for initial board display on move listing
3496        of wild games.
3497        */
3498     switch (ics_getting_history) {
3499       case H_FALSE:
3500       case H_REQUESTED:
3501         break;
3502       case H_GOT_REQ_HEADER:
3503       case H_GOT_UNREQ_HEADER:
3504         /* This is the initial position of the current game */
3505         gamenum = ics_gamenum;
3506         moveNum = 0;            /* old ICS bug workaround */
3507         if (to_play == 'B') {
3508           startedFromSetupPosition = TRUE;
3509           blackPlaysFirst = TRUE;
3510           moveNum = 1;
3511           if (forwardMostMove == 0) forwardMostMove = 1;
3512           if (backwardMostMove == 0) backwardMostMove = 1;
3513           if (currentMove == 0) currentMove = 1;
3514         }
3515         newGameMode = gameMode;
3516         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3517         break;
3518       case H_GOT_UNWANTED_HEADER:
3519         /* This is an initial board that we don't want */
3520         return;
3521       case H_GETTING_MOVES:
3522         /* Should not happen */
3523         DisplayError(_("Error gathering move list: extra board"), 0);
3524         ics_getting_history = H_FALSE;
3525         return;
3526     }
3527     
3528     /* Take action if this is the first board of a new game, or of a
3529        different game than is currently being displayed.  */
3530     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3531         relation == RELATION_ISOLATED_BOARD) {
3532         
3533         /* Forget the old game and get the history (if any) of the new one */
3534         if (gameMode != BeginningOfGame) {
3535           Reset(FALSE, TRUE);
3536         }
3537         newGame = TRUE;
3538         if (appData.autoRaiseBoard) BoardToTop();
3539         prevMove = -3;
3540         if (gamenum == -1) {
3541             newGameMode = IcsIdle;
3542         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3543                    appData.getMoveList) {
3544             /* Need to get game history */
3545             ics_getting_history = H_REQUESTED;
3546             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3547             SendToICS(str);
3548         }
3549         
3550         /* Initially flip the board to have black on the bottom if playing
3551            black or if the ICS flip flag is set, but let the user change
3552            it with the Flip View button. */
3553         flipView = appData.autoFlipView ? 
3554           (newGameMode == IcsPlayingBlack) || ics_flip :
3555           appData.flipView;
3556         
3557         /* Done with values from previous mode; copy in new ones */
3558         gameMode = newGameMode;
3559         ModeHighlight();
3560         ics_gamenum = gamenum;
3561         if (gamenum == gs_gamenum) {
3562             int klen = strlen(gs_kind);
3563             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3564             sprintf(str, "ICS %s", gs_kind);
3565             gameInfo.event = StrSave(str);
3566         } else {
3567             gameInfo.event = StrSave("ICS game");
3568         }
3569         gameInfo.site = StrSave(appData.icsHost);
3570         gameInfo.date = PGNDate();
3571         gameInfo.round = StrSave("-");
3572         gameInfo.white = StrSave(white);
3573         gameInfo.black = StrSave(black);
3574         timeControl = basetime * 60 * 1000;
3575         timeControl_2 = 0;
3576         timeIncrement = increment * 1000;
3577         movesPerSession = 0;
3578         gameInfo.timeControl = TimeControlTagValue();
3579         VariantSwitch(board, StringToVariant(gameInfo.event) );
3580   if (appData.debugMode) {
3581     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3582     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3583     setbuf(debugFP, NULL);
3584   }
3585
3586         gameInfo.outOfBook = NULL;
3587         
3588         /* Do we have the ratings? */
3589         if (strcmp(player1Name, white) == 0 &&
3590             strcmp(player2Name, black) == 0) {
3591             if (appData.debugMode)
3592               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3593                       player1Rating, player2Rating);
3594             gameInfo.whiteRating = player1Rating;
3595             gameInfo.blackRating = player2Rating;
3596         } else if (strcmp(player2Name, white) == 0 &&
3597                    strcmp(player1Name, black) == 0) {
3598             if (appData.debugMode)
3599               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3600                       player2Rating, player1Rating);
3601             gameInfo.whiteRating = player2Rating;
3602             gameInfo.blackRating = player1Rating;
3603         }
3604         player1Name[0] = player2Name[0] = NULLCHAR;
3605
3606         /* Silence shouts if requested */
3607         if (appData.quietPlay &&
3608             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3609             SendToICS(ics_prefix);
3610             SendToICS("set shout 0\n");
3611         }
3612     }
3613     
3614     /* Deal with midgame name changes */
3615     if (!newGame) {
3616         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3617             if (gameInfo.white) free(gameInfo.white);
3618             gameInfo.white = StrSave(white);
3619         }
3620         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3621             if (gameInfo.black) free(gameInfo.black);
3622             gameInfo.black = StrSave(black);
3623         }
3624     }
3625     
3626     /* Throw away game result if anything actually changes in examine mode */
3627     if (gameMode == IcsExamining && !newGame) {
3628         gameInfo.result = GameUnfinished;
3629         if (gameInfo.resultDetails != NULL) {
3630             free(gameInfo.resultDetails);
3631             gameInfo.resultDetails = NULL;
3632         }
3633     }
3634     
3635     /* In pausing && IcsExamining mode, we ignore boards coming
3636        in if they are in a different variation than we are. */
3637     if (pauseExamInvalid) return;
3638     if (pausing && gameMode == IcsExamining) {
3639         if (moveNum <= pauseExamForwardMostMove) {
3640             pauseExamInvalid = TRUE;
3641             forwardMostMove = pauseExamForwardMostMove;
3642             return;
3643         }
3644     }
3645     
3646   if (appData.debugMode) {
3647     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3648   }
3649     /* Parse the board */
3650     for (k = 0; k < ranks; k++) {
3651       for (j = 0; j < files; j++)
3652         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3653       if(gameInfo.holdingsWidth > 1) {
3654            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3655            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3656       }
3657     }
3658     CopyBoard(boards[moveNum], board);
3659     if (moveNum == 0) {
3660         startedFromSetupPosition =
3661           !CompareBoards(board, initialPosition);
3662         if(startedFromSetupPosition)
3663             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3664     }
3665
3666     /* [HGM] Set castling rights. Take the outermost Rooks,
3667        to make it also work for FRC opening positions. Note that board12
3668        is really defective for later FRC positions, as it has no way to
3669        indicate which Rook can castle if they are on the same side of King.
3670        For the initial position we grant rights to the outermost Rooks,
3671        and remember thos rights, and we then copy them on positions
3672        later in an FRC game. This means WB might not recognize castlings with
3673        Rooks that have moved back to their original position as illegal,
3674        but in ICS mode that is not its job anyway.
3675     */
3676     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3677     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3678
3679         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3680             if(board[0][i] == WhiteRook) j = i;
3681         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3682         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3683             if(board[0][i] == WhiteRook) j = i;
3684         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3686             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3687         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691
3692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696             if(board[BOARD_HEIGHT-1][k] == bKing)
3697                 initialRights[5] = castlingRights[moveNum][5] = k;
3698     } else { int r;
3699         r = castlingRights[moveNum][0] = initialRights[0];
3700         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3701         r = castlingRights[moveNum][1] = initialRights[1];
3702         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3703         r = castlingRights[moveNum][3] = initialRights[3];
3704         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3705         r = castlingRights[moveNum][4] = initialRights[4];
3706         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3707         /* wildcastle kludge: always assume King has rights */
3708         r = castlingRights[moveNum][2] = initialRights[2];
3709         r = castlingRights[moveNum][5] = initialRights[5];
3710     }
3711     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3712     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3713
3714     
3715     if (ics_getting_history == H_GOT_REQ_HEADER ||
3716         ics_getting_history == H_GOT_UNREQ_HEADER) {
3717         /* This was an initial position from a move list, not
3718            the current position */
3719         return;
3720     }
3721     
3722     /* Update currentMove and known move number limits */
3723     newMove = newGame || moveNum > forwardMostMove;
3724
3725     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3726     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3727         takeback = forwardMostMove - moveNum;
3728         for (i = 0; i < takeback; i++) {
3729              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3730              SendToProgram("undo\n", &first);
3731         }
3732     }
3733
3734     if (newGame) {
3735         forwardMostMove = backwardMostMove = currentMove = moveNum;
3736         if (gameMode == IcsExamining && moveNum == 0) {
3737           /* Workaround for ICS limitation: we are not told the wild
3738              type when starting to examine a game.  But if we ask for
3739              the move list, the move list header will tell us */
3740             ics_getting_history = H_REQUESTED;
3741             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3742             SendToICS(str);
3743         }
3744     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3745                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3746         forwardMostMove = moveNum;
3747         if (!pausing || currentMove > forwardMostMove)
3748           currentMove = forwardMostMove;
3749     } else {
3750         /* New part of history that is not contiguous with old part */ 
3751         if (pausing && gameMode == IcsExamining) {
3752             pauseExamInvalid = TRUE;
3753             forwardMostMove = pauseExamForwardMostMove;
3754             return;
3755         }
3756         forwardMostMove = backwardMostMove = currentMove = moveNum;
3757         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3758             ics_getting_history = H_REQUESTED;
3759             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3760             SendToICS(str);
3761         }
3762     }
3763     
3764     /* Update the clocks */
3765     if (strchr(elapsed_time, '.')) {
3766       /* Time is in ms */
3767       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3768       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3769     } else {
3770       /* Time is in seconds */
3771       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3772       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3773     }
3774       
3775
3776 #if ZIPPY
3777     if (appData.zippyPlay && newGame &&
3778         gameMode != IcsObserving && gameMode != IcsIdle &&
3779         gameMode != IcsExamining)
3780       ZippyFirstBoard(moveNum, basetime, increment);
3781 #endif
3782     
3783     /* Put the move on the move list, first converting
3784        to canonical algebraic form. */
3785     if (moveNum > 0) {
3786   if (appData.debugMode) {
3787     if (appData.debugMode) { int f = forwardMostMove;
3788         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3789                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3790     }
3791     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3792     fprintf(debugFP, "moveNum = %d\n", moveNum);
3793     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3794     setbuf(debugFP, NULL);
3795   }
3796         if (moveNum <= backwardMostMove) {
3797             /* We don't know what the board looked like before
3798                this move.  Punt. */
3799             strcpy(parseList[moveNum - 1], move_str);
3800             strcat(parseList[moveNum - 1], " ");
3801             strcat(parseList[moveNum - 1], elapsed_time);
3802             moveList[moveNum - 1][0] = NULLCHAR;
3803         } else if (strcmp(move_str, "none") == 0) {
3804             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3805             /* Again, we don't know what the board looked like;
3806                this is really the start of the game. */
3807             parseList[moveNum - 1][0] = NULLCHAR;
3808             moveList[moveNum - 1][0] = NULLCHAR;
3809             backwardMostMove = moveNum;
3810             startedFromSetupPosition = TRUE;
3811             fromX = fromY = toX = toY = -1;
3812         } else {
3813           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3814           //                 So we parse the long-algebraic move string in stead of the SAN move
3815           int valid; char buf[MSG_SIZ], *prom;
3816
3817           // str looks something like "Q/a1-a2"; kill the slash
3818           if(str[1] == '/') 
3819                 sprintf(buf, "%c%s", str[0], str+2);
3820           else  strcpy(buf, str); // might be castling
3821           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3822                 strcat(buf, prom); // long move lacks promo specification!
3823           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3824                 if(appData.debugMode) 
3825                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3826                 strcpy(move_str, buf);
3827           }
3828           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3829                                 &fromX, &fromY, &toX, &toY, &promoChar)
3830                || ParseOneMove(buf, moveNum - 1, &moveType,
3831                                 &fromX, &fromY, &toX, &toY, &promoChar);
3832           // end of long SAN patch
3833           if (valid) {
3834             (void) CoordsToAlgebraic(boards[moveNum - 1],
3835                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3836                                      fromY, fromX, toY, toX, promoChar,
3837                                      parseList[moveNum-1]);
3838             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3839                              castlingRights[moveNum]) ) {
3840               case MT_NONE:
3841               case MT_STALEMATE:
3842               default:
3843                 break;
3844               case MT_CHECK:
3845                 if(gameInfo.variant != VariantShogi)
3846                     strcat(parseList[moveNum - 1], "+");
3847                 break;
3848               case MT_CHECKMATE:
3849               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3850                 strcat(parseList[moveNum - 1], "#");
3851                 break;
3852             }
3853             strcat(parseList[moveNum - 1], " ");
3854             strcat(parseList[moveNum - 1], elapsed_time);
3855             /* currentMoveString is set as a side-effect of ParseOneMove */
3856             strcpy(moveList[moveNum - 1], currentMoveString);
3857             strcat(moveList[moveNum - 1], "\n");
3858           } else {
3859             /* Move from ICS was illegal!?  Punt. */
3860   if (appData.debugMode) {
3861     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3862     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3863   }
3864             strcpy(parseList[moveNum - 1], move_str);
3865             strcat(parseList[moveNum - 1], " ");
3866             strcat(parseList[moveNum - 1], elapsed_time);
3867             moveList[moveNum - 1][0] = NULLCHAR;
3868             fromX = fromY = toX = toY = -1;
3869           }
3870         }
3871   if (appData.debugMode) {
3872     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3873     setbuf(debugFP, NULL);
3874   }
3875
3876 #if ZIPPY
3877         /* Send move to chess program (BEFORE animating it). */
3878         if (appData.zippyPlay && !newGame && newMove && 
3879            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3880
3881             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3882                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3883                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3884                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3885                             move_str);
3886                     DisplayError(str, 0);
3887                 } else {
3888                     if (first.sendTime) {
3889                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3890                     }
3891                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3892                     if (firstMove && !bookHit) {
3893                         firstMove = FALSE;
3894                         if (first.useColors) {
3895                           SendToProgram(gameMode == IcsPlayingWhite ?
3896                                         "white\ngo\n" :
3897                                         "black\ngo\n", &first);
3898                         } else {
3899                           SendToProgram("go\n", &first);
3900                         }
3901                         first.maybeThinking = TRUE;
3902                     }
3903                 }
3904             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3905               if (moveList[moveNum - 1][0] == NULLCHAR) {
3906                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3907                 DisplayError(str, 0);
3908               } else {
3909                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3910                 SendMoveToProgram(moveNum - 1, &first);
3911               }
3912             }
3913         }
3914 #endif
3915     }
3916
3917     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3918         /* If move comes from a remote source, animate it.  If it
3919            isn't remote, it will have already been animated. */
3920         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3921             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3922         }
3923         if (!pausing && appData.highlightLastMove) {
3924             SetHighlights(fromX, fromY, toX, toY);
3925         }
3926     }
3927     
3928     /* Start the clocks */
3929     whiteFlag = blackFlag = FALSE;
3930     appData.clockMode = !(basetime == 0 && increment == 0);
3931     if (ticking == 0) {
3932       ics_clock_paused = TRUE;
3933       StopClocks();
3934     } else if (ticking == 1) {
3935       ics_clock_paused = FALSE;
3936     }
3937     if (gameMode == IcsIdle ||
3938         relation == RELATION_OBSERVING_STATIC ||
3939         relation == RELATION_EXAMINING ||
3940         ics_clock_paused)
3941       DisplayBothClocks();
3942     else
3943       StartClocks();
3944     
3945     /* Display opponents and material strengths */
3946     if (gameInfo.variant != VariantBughouse &&
3947         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3948         if (tinyLayout || smallLayout) {
3949             if(gameInfo.variant == VariantNormal)
3950                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3951                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3952                     basetime, increment);
3953             else
3954                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3955                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3956                     basetime, increment, (int) gameInfo.variant);
3957         } else {
3958             if(gameInfo.variant == VariantNormal)
3959                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3960                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3961                     basetime, increment);
3962             else
3963                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3964                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3965                     basetime, increment, VariantName(gameInfo.variant));
3966         }
3967         DisplayTitle(str);
3968   if (appData.debugMode) {
3969     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3970   }
3971     }
3972
3973    
3974     /* Display the board */
3975     if (!pausing && !appData.noGUI) {
3976       
3977       if (appData.premove)
3978           if (!gotPremove || 
3979              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3980              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3981               ClearPremoveHighlights();
3982
3983       DrawPosition(FALSE, boards[currentMove]);
3984       DisplayMove(moveNum - 1);
3985       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3986             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3987               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3988         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3989       }
3990     }
3991
3992     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3993 #if ZIPPY
3994     if(bookHit) { // [HGM] book: simulate book reply
3995         static char bookMove[MSG_SIZ]; // a bit generous?
3996
3997         programStats.nodes = programStats.depth = programStats.time = 
3998         programStats.score = programStats.got_only_move = 0;
3999         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4000
4001         strcpy(bookMove, "move ");
4002         strcat(bookMove, bookHit);
4003         HandleMachineMove(bookMove, &first);
4004     }
4005 #endif
4006 }
4007
4008 void
4009 GetMoveListEvent()
4010 {
4011     char buf[MSG_SIZ];
4012     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4013         ics_getting_history = H_REQUESTED;
4014         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4015         SendToICS(buf);
4016     }
4017 }
4018
4019 void
4020 AnalysisPeriodicEvent(force)
4021      int force;
4022 {
4023     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4024          && !force) || !appData.periodicUpdates)
4025       return;
4026
4027     /* Send . command to Crafty to collect stats */
4028     SendToProgram(".\n", &first);
4029
4030     /* Don't send another until we get a response (this makes
4031        us stop sending to old Crafty's which don't understand
4032        the "." command (sending illegal cmds resets node count & time,
4033        which looks bad)) */
4034     programStats.ok_to_send = 0;
4035 }
4036
4037 void ics_update_width(new_width)
4038         int new_width;
4039 {
4040         ics_printf("set width %d\n", new_width);
4041 }
4042
4043 void
4044 SendMoveToProgram(moveNum, cps)
4045      int moveNum;
4046      ChessProgramState *cps;
4047 {
4048     char buf[MSG_SIZ];
4049
4050     if (cps->useUsermove) {
4051       SendToProgram("usermove ", cps);
4052     }
4053     if (cps->useSAN) {
4054       char *space;
4055       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4056         int len = space - parseList[moveNum];
4057         memcpy(buf, parseList[moveNum], len);
4058         buf[len++] = '\n';
4059         buf[len] = NULLCHAR;
4060       } else {
4061         sprintf(buf, "%s\n", parseList[moveNum]);
4062       }
4063       SendToProgram(buf, cps);
4064     } else {
4065       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4066         AlphaRank(moveList[moveNum], 4);
4067         SendToProgram(moveList[moveNum], cps);
4068         AlphaRank(moveList[moveNum], 4); // and back
4069       } else
4070       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4071        * the engine. It would be nice to have a better way to identify castle 
4072        * moves here. */
4073       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4074                                                                          && cps->useOOCastle) {
4075         int fromX = moveList[moveNum][0] - AAA; 
4076         int fromY = moveList[moveNum][1] - ONE;
4077         int toX = moveList[moveNum][2] - AAA; 
4078         int toY = moveList[moveNum][3] - ONE;
4079         if((boards[moveNum][fromY][fromX] == WhiteKing 
4080             && boards[moveNum][toY][toX] == WhiteRook)
4081            || (boards[moveNum][fromY][fromX] == BlackKing 
4082                && boards[moveNum][toY][toX] == BlackRook)) {
4083           if(toX > fromX) SendToProgram("O-O\n", cps);
4084           else SendToProgram("O-O-O\n", cps);
4085         }
4086         else SendToProgram(moveList[moveNum], cps);
4087       }
4088       else SendToProgram(moveList[moveNum], cps);
4089       /* End of additions by Tord */
4090     }
4091
4092     /* [HGM] setting up the opening has brought engine in force mode! */
4093     /*       Send 'go' if we are in a mode where machine should play. */
4094     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4095         (gameMode == TwoMachinesPlay   ||
4096 #ifdef ZIPPY
4097          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4098 #endif
4099          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4100         SendToProgram("go\n", cps);
4101   if (appData.debugMode) {
4102     fprintf(debugFP, "(extra)\n");
4103   }
4104     }
4105     setboardSpoiledMachineBlack = 0;
4106 }
4107
4108 void
4109 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4110      ChessMove moveType;
4111      int fromX, fromY, toX, toY;
4112 {
4113     char user_move[MSG_SIZ];
4114
4115     switch (moveType) {
4116       default:
4117         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4118                 (int)moveType, fromX, fromY, toX, toY);
4119         DisplayError(user_move + strlen("say "), 0);
4120         break;
4121       case WhiteKingSideCastle:
4122       case BlackKingSideCastle:
4123       case WhiteQueenSideCastleWild:
4124       case BlackQueenSideCastleWild:
4125       /* PUSH Fabien */
4126       case WhiteHSideCastleFR:
4127       case BlackHSideCastleFR:
4128       /* POP Fabien */
4129         sprintf(user_move, "o-o\n");
4130         break;
4131       case WhiteQueenSideCastle:
4132       case BlackQueenSideCastle:
4133       case WhiteKingSideCastleWild:
4134       case BlackKingSideCastleWild:
4135       /* PUSH Fabien */
4136       case WhiteASideCastleFR:
4137       case BlackASideCastleFR:
4138       /* POP Fabien */
4139         sprintf(user_move, "o-o-o\n");
4140         break;
4141       case WhitePromotionQueen:
4142       case BlackPromotionQueen:
4143       case WhitePromotionRook:
4144       case BlackPromotionRook:
4145       case WhitePromotionBishop:
4146       case BlackPromotionBishop:
4147       case WhitePromotionKnight:
4148       case BlackPromotionKnight:
4149       case WhitePromotionKing:
4150       case BlackPromotionKing:
4151       case WhitePromotionChancellor:
4152       case BlackPromotionChancellor:
4153       case WhitePromotionArchbishop:
4154       case BlackPromotionArchbishop:
4155         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4156             sprintf(user_move, "%c%c%c%c=%c\n",
4157                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4158                 PieceToChar(WhiteFerz));
4159         else if(gameInfo.variant == VariantGreat)
4160             sprintf(user_move, "%c%c%c%c=%c\n",
4161                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4162                 PieceToChar(WhiteMan));
4163         else
4164             sprintf(user_move, "%c%c%c%c=%c\n",
4165                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4166                 PieceToChar(PromoPiece(moveType)));
4167         break;
4168       case WhiteDrop:
4169       case BlackDrop:
4170         sprintf(user_move, "%c@%c%c\n",
4171                 ToUpper(PieceToChar((ChessSquare) fromX)),
4172                 AAA + toX, ONE + toY);
4173         break;
4174       case NormalMove:
4175       case WhiteCapturesEnPassant:
4176       case BlackCapturesEnPassant:
4177       case IllegalMove:  /* could be a variant we don't quite understand */
4178         sprintf(user_move, "%c%c%c%c\n",
4179                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4180         break;
4181     }
4182     SendToICS(user_move);
4183     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4184         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4185 }
4186
4187 void
4188 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4189      int rf, ff, rt, ft;
4190      char promoChar;
4191      char move[7];
4192 {
4193     if (rf == DROP_RANK) {
4194         sprintf(move, "%c@%c%c\n",
4195                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4196     } else {
4197         if (promoChar == 'x' || promoChar == NULLCHAR) {
4198             sprintf(move, "%c%c%c%c\n",
4199                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4200         } else {
4201             sprintf(move, "%c%c%c%c%c\n",
4202                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4203         }
4204     }
4205 }
4206
4207 void
4208 ProcessICSInitScript(f)
4209      FILE *f;
4210 {
4211     char buf[MSG_SIZ];
4212
4213     while (fgets(buf, MSG_SIZ, f)) {
4214         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4215     }
4216
4217     fclose(f);
4218 }
4219
4220
4221 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4222 void
4223 AlphaRank(char *move, int n)
4224 {
4225 //    char *p = move, c; int x, y;
4226
4227     if (appData.debugMode) {
4228         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4229     }
4230
4231     if(move[1]=='*' && 
4232        move[2]>='0' && move[2]<='9' &&
4233        move[3]>='a' && move[3]<='x'    ) {
4234         move[1] = '@';
4235         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4236         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4237     } else
4238     if(move[0]>='0' && move[0]<='9' &&
4239        move[1]>='a' && move[1]<='x' &&
4240        move[2]>='0' && move[2]<='9' &&
4241        move[3]>='a' && move[3]<='x'    ) {
4242         /* input move, Shogi -> normal */
4243         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4244         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4245         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4246         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4247     } else
4248     if(move[1]=='@' &&
4249        move[3]>='0' && move[3]<='9' &&
4250        move[2]>='a' && move[2]<='x'    ) {
4251         move[1] = '*';
4252         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4253         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4254     } else
4255     if(
4256        move[0]>='a' && move[0]<='x' &&
4257        move[3]>='0' && move[3]<='9' &&
4258        move[2]>='a' && move[2]<='x'    ) {
4259          /* output move, normal -> Shogi */
4260         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4261         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4262         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4263         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4264         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4265     }
4266     if (appData.debugMode) {
4267         fprintf(debugFP, "   out = '%s'\n", move);
4268     }
4269 }
4270
4271 /* Parser for moves from gnuchess, ICS, or user typein box */
4272 Boolean
4273 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4274      char *move;
4275      int moveNum;
4276      ChessMove *moveType;
4277      int *fromX, *fromY, *toX, *toY;
4278      char *promoChar;
4279 {       
4280     if (appData.debugMode) {
4281         fprintf(debugFP, "move to parse: %s\n", move);
4282     }
4283     *moveType = yylexstr(moveNum, move);
4284
4285     switch (*moveType) {
4286       case WhitePromotionChancellor:
4287       case BlackPromotionChancellor:
4288       case WhitePromotionArchbishop:
4289       case BlackPromotionArchbishop:
4290       case WhitePromotionQueen:
4291       case BlackPromotionQueen:
4292       case WhitePromotionRook:
4293       case BlackPromotionRook:
4294       case WhitePromotionBishop:
4295       case BlackPromotionBishop:
4296       case WhitePromotionKnight:
4297       case BlackPromotionKnight:
4298       case WhitePromotionKing:
4299       case BlackPromotionKing:
4300       case NormalMove:
4301       case WhiteCapturesEnPassant:
4302       case BlackCapturesEnPassant:
4303       case WhiteKingSideCastle:
4304       case WhiteQueenSideCastle:
4305       case BlackKingSideCastle:
4306       case BlackQueenSideCastle:
4307       case WhiteKingSideCastleWild:
4308       case WhiteQueenSideCastleWild:
4309       case BlackKingSideCastleWild:
4310       case BlackQueenSideCastleWild:
4311       /* Code added by Tord: */
4312       case WhiteHSideCastleFR:
4313       case WhiteASideCastleFR:
4314       case BlackHSideCastleFR:
4315       case BlackASideCastleFR:
4316       /* End of code added by Tord */
4317       case IllegalMove:         /* bug or odd chess variant */
4318         *fromX = currentMoveString[0] - AAA;
4319         *fromY = currentMoveString[1] - ONE;
4320         *toX = currentMoveString[2] - AAA;
4321         *toY = currentMoveString[3] - ONE;
4322         *promoChar = currentMoveString[4];
4323         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4324             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4325     if (appData.debugMode) {
4326         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4327     }
4328             *fromX = *fromY = *toX = *toY = 0;
4329             return FALSE;
4330         }
4331         if (appData.testLegality) {
4332           return (*moveType != IllegalMove);
4333         } else {
4334           return !(fromX == fromY && toX == toY);
4335         }
4336
4337       case WhiteDrop:
4338       case BlackDrop:
4339         *fromX = *moveType == WhiteDrop ?
4340           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4341           (int) CharToPiece(ToLower(currentMoveString[0]));
4342         *fromY = DROP_RANK;
4343         *toX = currentMoveString[2] - AAA;
4344         *toY = currentMoveString[3] - ONE;
4345         *promoChar = NULLCHAR;
4346         return TRUE;
4347
4348       case AmbiguousMove:
4349       case ImpossibleMove:
4350       case (ChessMove) 0:       /* end of file */
4351       case ElapsedTime:
4352       case Comment:
4353       case PGNTag:
4354       case NAG:
4355       case WhiteWins:
4356       case BlackWins:
4357       case GameIsDrawn:
4358       default:
4359     if (appData.debugMode) {
4360         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4361     }
4362         /* bug? */
4363         *fromX = *fromY = *toX = *toY = 0;
4364         *promoChar = NULLCHAR;
4365         return FALSE;
4366     }
4367 }
4368
4369 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4370 // All positions will have equal probability, but the current method will not provide a unique
4371 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4372 #define DARK 1
4373 #define LITE 2
4374 #define ANY 3
4375
4376 int squaresLeft[4];
4377 int piecesLeft[(int)BlackPawn];
4378 int seed, nrOfShuffles;
4379
4380 void GetPositionNumber()
4381 {       // sets global variable seed
4382         int i;
4383
4384         seed = appData.defaultFrcPosition;
4385         if(seed < 0) { // randomize based on time for negative FRC position numbers
4386                 for(i=0; i<50; i++) seed += random();
4387                 seed = random() ^ random() >> 8 ^ random() << 8;
4388                 if(seed<0) seed = -seed;
4389         }
4390 }
4391
4392 int put(Board board, int pieceType, int rank, int n, int shade)
4393 // put the piece on the (n-1)-th empty squares of the given shade
4394 {
4395         int i;
4396
4397         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4398                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4399                         board[rank][i] = (ChessSquare) pieceType;
4400                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4401                         squaresLeft[ANY]--;
4402                         piecesLeft[pieceType]--; 
4403                         return i;
4404                 }
4405         }
4406         return -1;
4407 }
4408
4409
4410 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4411 // calculate where the next piece goes, (any empty square), and put it there
4412 {
4413         int i;
4414
4415         i = seed % squaresLeft[shade];
4416         nrOfShuffles *= squaresLeft[shade];
4417         seed /= squaresLeft[shade];
4418         put(board, pieceType, rank, i, shade);
4419 }
4420
4421 void AddTwoPieces(Board board, int pieceType, int rank)
4422 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4423 {
4424         int i, n=squaresLeft[ANY], j=n-1, k;
4425
4426         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4427         i = seed % k;  // pick one
4428         nrOfShuffles *= k;
4429         seed /= k;
4430         while(i >= j) i -= j--;
4431         j = n - 1 - j; i += j;
4432         put(board, pieceType, rank, j, ANY);
4433         put(board, pieceType, rank, i, ANY);
4434 }
4435
4436 void SetUpShuffle(Board board, int number)
4437 {
4438         int i, p, first=1;
4439
4440         GetPositionNumber(); nrOfShuffles = 1;
4441
4442         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4443         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4444         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4445
4446         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4447
4448         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4449             p = (int) board[0][i];
4450             if(p < (int) BlackPawn) piecesLeft[p] ++;
4451             board[0][i] = EmptySquare;
4452         }
4453
4454         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4455             // shuffles restricted to allow normal castling put KRR first
4456             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4457                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4458             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4459                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4460             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4461                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4462             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4463                 put(board, WhiteRook, 0, 0, ANY);
4464             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4465         }
4466
4467         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4468             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4469             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4470                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4471                 while(piecesLeft[p] >= 2) {
4472                     AddOnePiece(board, p, 0, LITE);
4473                     AddOnePiece(board, p, 0, DARK);
4474                 }
4475                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4476             }
4477
4478         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4479             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4480             // but we leave King and Rooks for last, to possibly obey FRC restriction
4481             if(p == (int)WhiteRook) continue;
4482             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4483             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4484         }
4485
4486         // now everything is placed, except perhaps King (Unicorn) and Rooks
4487
4488         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4489             // Last King gets castling rights
4490             while(piecesLeft[(int)WhiteUnicorn]) {
4491                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4492                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4493             }
4494
4495             while(piecesLeft[(int)WhiteKing]) {
4496                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4497                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4498             }
4499
4500
4501         } else {
4502             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4503             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4504         }
4505
4506         // Only Rooks can be left; simply place them all
4507         while(piecesLeft[(int)WhiteRook]) {
4508                 i = put(board, WhiteRook, 0, 0, ANY);
4509                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4510                         if(first) {
4511                                 first=0;
4512                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4513                         }
4514                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4515                 }
4516         }
4517         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4518             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4519         }
4520
4521         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4522 }
4523
4524 int SetCharTable( char *table, const char * map )
4525 /* [HGM] moved here from winboard.c because of its general usefulness */
4526 /*       Basically a safe strcpy that uses the last character as King */
4527 {
4528     int result = FALSE; int NrPieces;
4529
4530     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4531                     && NrPieces >= 12 && !(NrPieces&1)) {
4532         int i; /* [HGM] Accept even length from 12 to 34 */
4533
4534         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4535         for( i=0; i<NrPieces/2-1; i++ ) {
4536             table[i] = map[i];
4537             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4538         }
4539         table[(int) WhiteKing]  = map[NrPieces/2-1];
4540         table[(int) BlackKing]  = map[NrPieces-1];
4541
4542         result = TRUE;
4543     }
4544
4545     return result;
4546 }
4547
4548 void Prelude(Board board)
4549 {       // [HGM] superchess: random selection of exo-pieces
4550         int i, j, k; ChessSquare p; 
4551         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4552
4553         GetPositionNumber(); // use FRC position number
4554
4555         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4556             SetCharTable(pieceToChar, appData.pieceToCharTable);
4557             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4558                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4559         }
4560
4561         j = seed%4;                 seed /= 4; 
4562         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4563         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4564         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4565         j = seed%3 + (seed%3 >= j); seed /= 3; 
4566         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4567         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4568         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4569         j = seed%3;                 seed /= 3; 
4570         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4571         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4572         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4573         j = seed%2 + (seed%2 >= j); seed /= 2; 
4574         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4575         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4576         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4577         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4578         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4579         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4580         put(board, exoPieces[0],    0, 0, ANY);
4581         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4582 }
4583
4584 void
4585 InitPosition(redraw)
4586      int redraw;
4587 {
4588     ChessSquare (* pieces)[BOARD_SIZE];
4589     int i, j, pawnRow, overrule,
4590     oldx = gameInfo.boardWidth,
4591     oldy = gameInfo.boardHeight,
4592     oldh = gameInfo.holdingsWidth,
4593     oldv = gameInfo.variant;
4594
4595     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4596
4597     /* [AS] Initialize pv info list [HGM] and game status */
4598     {
4599         for( i=0; i<MAX_MOVES; i++ ) {
4600             pvInfoList[i].depth = 0;
4601             epStatus[i]=EP_NONE;
4602             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4603         }
4604
4605         initialRulePlies = 0; /* 50-move counter start */
4606
4607         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4608         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4609     }
4610
4611     
4612     /* [HGM] logic here is completely changed. In stead of full positions */
4613     /* the initialized data only consist of the two backranks. The switch */
4614     /* selects which one we will use, which is than copied to the Board   */
4615     /* initialPosition, which for the rest is initialized by Pawns and    */
4616     /* empty squares. This initial position is then copied to boards[0],  */
4617     /* possibly after shuffling, so that it remains available.            */
4618
4619     gameInfo.holdingsWidth = 0; /* default board sizes */
4620     gameInfo.boardWidth    = 8;
4621     gameInfo.boardHeight   = 8;
4622     gameInfo.holdingsSize  = 0;
4623     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4624     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4625     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4626
4627     switch (gameInfo.variant) {
4628     case VariantFischeRandom:
4629       shuffleOpenings = TRUE;
4630     default:
4631       pieces = FIDEArray;
4632       break;
4633     case VariantShatranj:
4634       pieces = ShatranjArray;
4635       nrCastlingRights = 0;
4636       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4637       break;
4638     case VariantTwoKings:
4639       pieces = twoKingsArray;
4640       break;
4641     case VariantCapaRandom:
4642       shuffleOpenings = TRUE;
4643     case VariantCapablanca:
4644       pieces = CapablancaArray;
4645       gameInfo.boardWidth = 10;
4646       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4647       break;
4648     case VariantGothic:
4649       pieces = GothicArray;
4650       gameInfo.boardWidth = 10;
4651       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4652       break;
4653     case VariantJanus:
4654       pieces = JanusArray;
4655       gameInfo.boardWidth = 10;
4656       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4657       nrCastlingRights = 6;
4658         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4659         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4660         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4661         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4662         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4663         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4664       break;
4665     case VariantFalcon:
4666       pieces = FalconArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4669       break;
4670     case VariantXiangqi:
4671       pieces = XiangqiArray;
4672       gameInfo.boardWidth  = 9;
4673       gameInfo.boardHeight = 10;
4674       nrCastlingRights = 0;
4675       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4676       break;
4677     case VariantShogi:
4678       pieces = ShogiArray;
4679       gameInfo.boardWidth  = 9;
4680       gameInfo.boardHeight = 9;
4681       gameInfo.holdingsSize = 7;
4682       nrCastlingRights = 0;
4683       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4684       break;
4685     case VariantCourier:
4686       pieces = CourierArray;
4687       gameInfo.boardWidth  = 12;
4688       nrCastlingRights = 0;
4689       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4690       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4691       break;
4692     case VariantKnightmate:
4693       pieces = KnightmateArray;
4694       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4695       break;
4696     case VariantFairy:
4697       pieces = fairyArray;
4698       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4699       break;
4700     case VariantGreat:
4701       pieces = GreatArray;
4702       gameInfo.boardWidth = 10;
4703       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4704       gameInfo.holdingsSize = 8;
4705       break;
4706     case VariantSuper:
4707       pieces = FIDEArray;
4708       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4709       gameInfo.holdingsSize = 8;
4710       startedFromSetupPosition = TRUE;
4711       break;
4712     case VariantCrazyhouse:
4713     case VariantBughouse:
4714       pieces = FIDEArray;
4715       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4716       gameInfo.holdingsSize = 5;
4717       break;
4718     case VariantWildCastle:
4719       pieces = FIDEArray;
4720       /* !!?shuffle with kings guaranteed to be on d or e file */
4721       shuffleOpenings = 1;
4722       break;
4723     case VariantNoCastle:
4724       pieces = FIDEArray;
4725       nrCastlingRights = 0;
4726       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4727       /* !!?unconstrained back-rank shuffle */
4728       shuffleOpenings = 1;
4729       break;
4730     }
4731
4732     overrule = 0;
4733     if(appData.NrFiles >= 0) {
4734         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4735         gameInfo.boardWidth = appData.NrFiles;
4736     }
4737     if(appData.NrRanks >= 0) {
4738         gameInfo.boardHeight = appData.NrRanks;
4739     }
4740     if(appData.holdingsSize >= 0) {
4741         i = appData.holdingsSize;
4742         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4743         gameInfo.holdingsSize = i;
4744     }
4745     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4746     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4747         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4748
4749     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4750     if(pawnRow < 1) pawnRow = 1;
4751
4752     /* User pieceToChar list overrules defaults */
4753     if(appData.pieceToCharTable != NULL)
4754         SetCharTable(pieceToChar, appData.pieceToCharTable);
4755
4756     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4757
4758         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4759             s = (ChessSquare) 0; /* account holding counts in guard band */
4760         for( i=0; i<BOARD_HEIGHT; i++ )
4761             initialPosition[i][j] = s;
4762
4763         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4764         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4765         initialPosition[pawnRow][j] = WhitePawn;
4766         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4767         if(gameInfo.variant == VariantXiangqi) {
4768             if(j&1) {
4769                 initialPosition[pawnRow][j] = 
4770                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4771                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4772                    initialPosition[2][j] = WhiteCannon;
4773                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4774                 }
4775             }
4776         }
4777         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4778     }
4779     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4780
4781             j=BOARD_LEFT+1;
4782             initialPosition[1][j] = WhiteBishop;
4783             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4784             j=BOARD_RGHT-2;
4785             initialPosition[1][j] = WhiteRook;
4786             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4787     }
4788
4789     if( nrCastlingRights == -1) {
4790         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4791         /*       This sets default castling rights from none to normal corners   */
4792         /* Variants with other castling rights must set them themselves above    */
4793         nrCastlingRights = 6;
4794        
4795         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4796         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4797         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4798         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4799         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4800         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4801      }
4802
4803      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4804      if(gameInfo.variant == VariantGreat) { // promotion commoners
4805         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4806         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4807         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4808         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4809      }
4810   if (appData.debugMode) {
4811     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4812   }
4813     if(shuffleOpenings) {
4814         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4815         startedFromSetupPosition = TRUE;
4816     }
4817     if(startedFromPositionFile) {
4818       /* [HGM] loadPos: use PositionFile for every new game */
4819       CopyBoard(initialPosition, filePosition);
4820       for(i=0; i<nrCastlingRights; i++)
4821           castlingRights[0][i] = initialRights[i] = fileRights[i];
4822       startedFromSetupPosition = TRUE;
4823     }
4824
4825     CopyBoard(boards[0], initialPosition);
4826
4827     if(oldx != gameInfo.boardWidth ||
4828        oldy != gameInfo.boardHeight ||
4829        oldh != gameInfo.holdingsWidth
4830 #ifdef GOTHIC
4831        || oldv == VariantGothic ||        // For licensing popups
4832        gameInfo.variant == VariantGothic
4833 #endif
4834 #ifdef FALCON
4835        || oldv == VariantFalcon ||
4836        gameInfo.variant == VariantFalcon
4837 #endif
4838                                          )
4839             InitDrawingSizes(-2 ,0);
4840
4841     if (redraw)
4842       DrawPosition(TRUE, boards[currentMove]);
4843 }
4844
4845 void
4846 SendBoard(cps, moveNum)
4847      ChessProgramState *cps;
4848      int moveNum;
4849 {
4850     char message[MSG_SIZ];
4851     
4852     if (cps->useSetboard) {
4853       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4854       sprintf(message, "setboard %s\n", fen);
4855       SendToProgram(message, cps);
4856       free(fen);
4857
4858     } else {
4859       ChessSquare *bp;
4860       int i, j;
4861       /* Kludge to set black to move, avoiding the troublesome and now
4862        * deprecated "black" command.
4863        */
4864       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4865
4866       SendToProgram("edit\n", cps);
4867       SendToProgram("#\n", cps);
4868       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4869         bp = &boards[moveNum][i][BOARD_LEFT];
4870         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4871           if ((int) *bp < (int) BlackPawn) {
4872             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4873                     AAA + j, ONE + i);
4874             if(message[0] == '+' || message[0] == '~') {
4875                 sprintf(message, "%c%c%c+\n",
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4877                         AAA + j, ONE + i);
4878             }
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882             }
4883             SendToProgram(message, cps);
4884           }
4885         }
4886       }
4887     
4888       SendToProgram("c\n", cps);
4889       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4890         bp = &boards[moveNum][i][BOARD_LEFT];
4891         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4892           if (((int) *bp != (int) EmptySquare)
4893               && ((int) *bp >= (int) BlackPawn)) {
4894             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4895                     AAA + j, ONE + i);
4896             if(message[0] == '+' || message[0] == '~') {
4897                 sprintf(message, "%c%c%c+\n",
4898                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4899                         AAA + j, ONE + i);
4900             }
4901             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4902                 message[1] = BOARD_RGHT   - 1 - j + '1';
4903                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4904             }
4905             SendToProgram(message, cps);
4906           }
4907         }
4908       }
4909     
4910       SendToProgram(".\n", cps);
4911     }
4912     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4913 }
4914
4915 int
4916 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4917 {
4918     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4919     /* [HGM] add Shogi promotions */
4920     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4921     ChessSquare piece;
4922     ChessMove moveType;
4923     Boolean premove;
4924
4925     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4926     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4927
4928     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4929       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4930         return FALSE;
4931
4932     piece = boards[currentMove][fromY][fromX];
4933     if(gameInfo.variant == VariantShogi) {
4934         promotionZoneSize = 3;
4935         highestPromotingPiece = (int)WhiteFerz;
4936     }
4937
4938     // next weed out all moves that do not touch the promotion zone at all
4939     if((int)piece >= BlackPawn) {
4940         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4941              return FALSE;
4942         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4943     } else {
4944         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4945            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4946     }
4947
4948     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4949
4950     // weed out mandatory Shogi promotions
4951     if(gameInfo.variant == VariantShogi) {
4952         if(piece >= BlackPawn) {
4953             if(toY == 0 && piece == BlackPawn ||
4954                toY == 0 && piece == BlackQueen ||
4955                toY <= 1 && piece == BlackKnight) {
4956                 *promoChoice = '+';
4957                 return FALSE;
4958             }
4959         } else {
4960             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4961                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4962                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4963                 *promoChoice = '+';
4964                 return FALSE;
4965             }
4966         }
4967     }
4968
4969     // weed out obviously illegal Pawn moves
4970     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4971         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4972         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4973         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4974         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4975         // note we are not allowed to test for valid (non-)capture, due to premove
4976     }
4977
4978     // we either have a choice what to promote to, or (in Shogi) whether to promote
4979     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4980         *promoChoice = PieceToChar(BlackFerz);  // no choice
4981         return FALSE;
4982     }
4983     if(appData.alwaysPromoteToQueen) { // predetermined
4984         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4985              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4986         else *promoChoice = PieceToChar(BlackQueen);
4987         return FALSE;
4988     }
4989
4990     // suppress promotion popup on illegal moves that are not premoves
4991     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4992               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
4993     if(appData.testLegality && !premove) {
4994         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4995                         epStatus[currentMove], castlingRights[currentMove],
4996                         fromY, fromX, toY, toX, NULLCHAR);
4997         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
4998            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4999             return FALSE;
5000     }
5001
5002     return TRUE;
5003 }
5004
5005 int
5006 InPalace(row, column)
5007      int row, column;
5008 {   /* [HGM] for Xiangqi */
5009     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5010          column < (BOARD_WIDTH + 4)/2 &&
5011          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5012     return FALSE;
5013 }
5014
5015 int
5016 PieceForSquare (x, y)
5017      int x;
5018      int y;
5019 {
5020   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5021      return -1;
5022   else
5023      return boards[currentMove][y][x];
5024 }
5025
5026 int
5027 OKToStartUserMove(x, y)
5028      int x, y;
5029 {
5030     ChessSquare from_piece;
5031     int white_piece;
5032
5033     if (matchMode) return FALSE;
5034     if (gameMode == EditPosition) return TRUE;
5035
5036     if (x >= 0 && y >= 0)
5037       from_piece = boards[currentMove][y][x];
5038     else
5039       from_piece = EmptySquare;
5040
5041     if (from_piece == EmptySquare) return FALSE;
5042
5043     white_piece = (int)from_piece >= (int)WhitePawn &&
5044       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5045
5046     switch (gameMode) {
5047       case PlayFromGameFile:
5048       case AnalyzeFile:
5049       case TwoMachinesPlay:
5050       case EndOfGame:
5051         return FALSE;
5052
5053       case IcsObserving:
5054       case IcsIdle:
5055         return FALSE;
5056
5057       case MachinePlaysWhite:
5058       case IcsPlayingBlack:
5059         if (appData.zippyPlay) return FALSE;
5060         if (white_piece) {
5061             DisplayMoveError(_("You are playing Black"));
5062             return FALSE;
5063         }
5064         break;
5065
5066       case MachinePlaysBlack:
5067       case IcsPlayingWhite:
5068         if (appData.zippyPlay) return FALSE;
5069         if (!white_piece) {
5070             DisplayMoveError(_("You are playing White"));
5071             return FALSE;
5072         }
5073         break;
5074
5075       case EditGame:
5076         if (!white_piece && WhiteOnMove(currentMove)) {
5077             DisplayMoveError(_("It is White's turn"));
5078             return FALSE;
5079         }           
5080         if (white_piece && !WhiteOnMove(currentMove)) {
5081             DisplayMoveError(_("It is Black's turn"));
5082             return FALSE;
5083         }           
5084         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5085             /* Editing correspondence game history */
5086             /* Could disallow this or prompt for confirmation */
5087             cmailOldMove = -1;
5088         }
5089         if (currentMove < forwardMostMove) {
5090             /* Discarding moves */
5091             /* Could prompt for confirmation here,
5092                but I don't think that's such a good idea */
5093             forwardMostMove = currentMove;
5094         }
5095         break;
5096
5097       case BeginningOfGame:
5098         if (appData.icsActive) return FALSE;
5099         if (!appData.noChessProgram) {
5100             if (!white_piece) {
5101                 DisplayMoveError(_("You are playing White"));
5102                 return FALSE;
5103             }
5104         }
5105         break;
5106         
5107       case Training:
5108         if (!white_piece && WhiteOnMove(currentMove)) {
5109             DisplayMoveError(_("It is White's turn"));
5110             return FALSE;
5111         }           
5112         if (white_piece && !WhiteOnMove(currentMove)) {
5113             DisplayMoveError(_("It is Black's turn"));
5114             return FALSE;
5115         }           
5116         break;
5117
5118       default:
5119       case IcsExamining:
5120         break;
5121     }
5122     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5123         && gameMode != AnalyzeFile && gameMode != Training) {
5124         DisplayMoveError(_("Displayed position is not current"));
5125         return FALSE;
5126     }
5127     return TRUE;
5128 }
5129
5130 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5131 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5132 int lastLoadGameUseList = FALSE;
5133 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5134 ChessMove lastLoadGameStart = (ChessMove) 0;
5135
5136 ChessMove
5137 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5138      int fromX, fromY, toX, toY;
5139      int promoChar;
5140      Boolean captureOwn;
5141 {
5142     ChessMove moveType;
5143     ChessSquare pdown, pup;
5144
5145     /* Check if the user is playing in turn.  This is complicated because we
5146        let the user "pick up" a piece before it is his turn.  So the piece he
5147        tried to pick up may have been captured by the time he puts it down!
5148        Therefore we use the color the user is supposed to be playing in this
5149        test, not the color of the piece that is currently on the starting
5150        square---except in EditGame mode, where the user is playing both
5151        sides; fortunately there the capture race can't happen.  (It can
5152        now happen in IcsExamining mode, but that's just too bad.  The user
5153        will get a somewhat confusing message in that case.)
5154        */
5155
5156     switch (gameMode) {
5157       case PlayFromGameFile:
5158       case AnalyzeFile:
5159       case TwoMachinesPlay:
5160       case EndOfGame:
5161       case IcsObserving:
5162       case IcsIdle:
5163         /* We switched into a game mode where moves are not accepted,
5164            perhaps while the mouse button was down. */
5165         return ImpossibleMove;
5166
5167       case MachinePlaysWhite:
5168         /* User is moving for Black */
5169         if (WhiteOnMove(currentMove)) {
5170             DisplayMoveError(_("It is White's turn"));
5171             return ImpossibleMove;
5172         }
5173         break;
5174
5175       case MachinePlaysBlack:
5176         /* User is moving for White */
5177         if (!WhiteOnMove(currentMove)) {
5178             DisplayMoveError(_("It is Black's turn"));
5179             return ImpossibleMove;
5180         }
5181         break;
5182
5183       case EditGame:
5184       case IcsExamining:
5185       case BeginningOfGame:
5186       case AnalyzeMode:
5187       case Training:
5188         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5189             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5190             /* User is moving for Black */
5191             if (WhiteOnMove(currentMove)) {
5192                 DisplayMoveError(_("It is White's turn"));
5193                 return ImpossibleMove;
5194             }
5195         } else {
5196             /* User is moving for White */
5197             if (!WhiteOnMove(currentMove)) {
5198                 DisplayMoveError(_("It is Black's turn"));
5199                 return ImpossibleMove;
5200             }
5201         }
5202         break;
5203
5204       case IcsPlayingBlack:
5205         /* User is moving for Black */
5206         if (WhiteOnMove(currentMove)) {
5207             if (!appData.premove) {
5208                 DisplayMoveError(_("It is White's turn"));
5209             } else if (toX >= 0 && toY >= 0) {
5210                 premoveToX = toX;
5211                 premoveToY = toY;
5212                 premoveFromX = fromX;
5213                 premoveFromY = fromY;
5214                 premovePromoChar = promoChar;
5215                 gotPremove = 1;
5216                 if (appData.debugMode) 
5217                     fprintf(debugFP, "Got premove: fromX %d,"
5218                             "fromY %d, toX %d, toY %d\n",
5219                             fromX, fromY, toX, toY);
5220             }
5221             return ImpossibleMove;
5222         }
5223         break;
5224
5225       case IcsPlayingWhite:
5226         /* User is moving for White */
5227         if (!WhiteOnMove(currentMove)) {
5228             if (!appData.premove) {
5229                 DisplayMoveError(_("It is Black's turn"));
5230             } else if (toX >= 0 && toY >= 0) {
5231                 premoveToX = toX;
5232                 premoveToY = toY;
5233                 premoveFromX = fromX;
5234                 premoveFromY = fromY;
5235                 premovePromoChar = promoChar;
5236                 gotPremove = 1;
5237                 if (appData.debugMode) 
5238                     fprintf(debugFP, "Got premove: fromX %d,"
5239                             "fromY %d, toX %d, toY %d\n",
5240                             fromX, fromY, toX, toY);
5241             }
5242             return ImpossibleMove;
5243         }
5244         break;
5245
5246       default:
5247         break;
5248
5249       case EditPosition:
5250         /* EditPosition, empty square, or different color piece;
5251            click-click move is possible */
5252         if (toX == -2 || toY == -2) {
5253             boards[0][fromY][fromX] = EmptySquare;
5254             return AmbiguousMove;
5255         } else if (toX >= 0 && toY >= 0) {
5256             boards[0][toY][toX] = boards[0][fromY][fromX];
5257             boards[0][fromY][fromX] = EmptySquare;
5258             return AmbiguousMove;
5259         }
5260         return ImpossibleMove;
5261     }
5262
5263     pdown = boards[currentMove][fromY][fromX];
5264     pup = boards[currentMove][toY][toX];
5265
5266     /* [HGM] If move started in holdings, it means a drop */
5267     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5268          if( pup != EmptySquare ) return ImpossibleMove;
5269          if(appData.testLegality) {
5270              /* it would be more logical if LegalityTest() also figured out
5271               * which drops are legal. For now we forbid pawns on back rank.
5272               * Shogi is on its own here...
5273               */
5274              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5275                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5276                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5277          }
5278          return WhiteDrop; /* Not needed to specify white or black yet */
5279     }
5280
5281     userOfferedDraw = FALSE;
5282         
5283     /* [HGM] always test for legality, to get promotion info */
5284     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5285                           epStatus[currentMove], castlingRights[currentMove],
5286                                          fromY, fromX, toY, toX, promoChar);
5287     /* [HGM] but possibly ignore an IllegalMove result */
5288     if (appData.testLegality) {
5289         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5290             DisplayMoveError(_("Illegal move"));
5291             return ImpossibleMove;
5292         }
5293     }
5294 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5295     return moveType;
5296     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5297        function is made into one that returns an OK move type if FinishMove
5298        should be called. This to give the calling driver routine the
5299        opportunity to finish the userMove input with a promotion popup,
5300        without bothering the user with this for invalid or illegal moves */
5301
5302 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5303 }
5304
5305 /* Common tail of UserMoveEvent and DropMenuEvent */
5306 int
5307 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5308      ChessMove moveType;
5309      int fromX, fromY, toX, toY;
5310      /*char*/int promoChar;
5311 {
5312     char *bookHit = 0;
5313 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5314     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5315         // [HGM] superchess: suppress promotions to non-available piece
5316         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5317         if(WhiteOnMove(currentMove)) {
5318             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5319         } else {
5320             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5321         }
5322     }
5323
5324     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5325        move type in caller when we know the move is a legal promotion */
5326     if(moveType == NormalMove && promoChar)
5327         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5328 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5329     /* [HGM] convert drag-and-drop piece drops to standard form */
5330     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5331          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5332            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5333                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5334 //         fromX = boards[currentMove][fromY][fromX];
5335            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5336            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5337            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5338            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5339          fromY = DROP_RANK;
5340     }
5341
5342     /* [HGM] <popupFix> The following if has been moved here from
5343        UserMoveEvent(). Because it seemed to belon here (why not allow
5344        piece drops in training games?), and because it can only be
5345        performed after it is known to what we promote. */
5346     if (gameMode == Training) {
5347       /* compare the move played on the board to the next move in the
5348        * game. If they match, display the move and the opponent's response. 
5349        * If they don't match, display an error message.
5350        */
5351       int saveAnimate;
5352       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5353       CopyBoard(testBoard, boards[currentMove]);
5354       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5355
5356       if (CompareBoards(testBoard, boards[currentMove+1])) {
5357         ForwardInner(currentMove+1);
5358
5359         /* Autoplay the opponent's response.
5360          * if appData.animate was TRUE when Training mode was entered,
5361          * the response will be animated.
5362          */
5363         saveAnimate = appData.animate;
5364         appData.animate = animateTraining;
5365         ForwardInner(currentMove+1);
5366         appData.animate = saveAnimate;
5367
5368         /* check for the end of the game */
5369         if (currentMove >= forwardMostMove) {
5370           gameMode = PlayFromGameFile;
5371           ModeHighlight();
5372           SetTrainingModeOff();
5373           DisplayInformation(_("End of game"));
5374         }
5375       } else {
5376         DisplayError(_("Incorrect move"), 0);
5377       }
5378       return 1;
5379     }
5380
5381   /* Ok, now we know that the move is good, so we can kill
5382      the previous line in Analysis Mode */
5383   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5384     forwardMostMove = currentMove;
5385   }
5386
5387   /* If we need the chess program but it's dead, restart it */
5388   ResurrectChessProgram();
5389
5390   /* A user move restarts a paused game*/
5391   if (pausing)
5392     PauseEvent();
5393
5394   thinkOutput[0] = NULLCHAR;
5395
5396   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5397
5398   if (gameMode == BeginningOfGame) {
5399     if (appData.noChessProgram) {
5400       gameMode = EditGame;
5401       SetGameInfo();
5402     } else {
5403       char buf[MSG_SIZ];
5404       gameMode = MachinePlaysBlack;
5405       StartClocks();
5406       SetGameInfo();
5407       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5408       DisplayTitle(buf);
5409       if (first.sendName) {
5410         sprintf(buf, "name %s\n", gameInfo.white);
5411         SendToProgram(buf, &first);
5412       }
5413       StartClocks();
5414     }
5415     ModeHighlight();
5416   }
5417 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5418   /* Relay move to ICS or chess engine */
5419   if (appData.icsActive) {
5420     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5421         gameMode == IcsExamining) {
5422       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5423       ics_user_moved = 1;
5424     }
5425   } else {
5426     if (first.sendTime && (gameMode == BeginningOfGame ||
5427                            gameMode == MachinePlaysWhite ||
5428                            gameMode == MachinePlaysBlack)) {
5429       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5430     }
5431     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5432          // [HGM] book: if program might be playing, let it use book
5433         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5434         first.maybeThinking = TRUE;
5435     } else SendMoveToProgram(forwardMostMove-1, &first);
5436     if (currentMove == cmailOldMove + 1) {
5437       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5438     }
5439   }
5440
5441   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5442
5443   switch (gameMode) {
5444   case EditGame:
5445     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5446                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5447     case MT_NONE:
5448     case MT_CHECK:
5449       break;
5450     case MT_CHECKMATE:
5451     case MT_STAINMATE:
5452       if (WhiteOnMove(currentMove)) {
5453         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5454       } else {
5455         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5456       }
5457       break;
5458     case MT_STALEMATE:
5459       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5460       break;
5461     }
5462     break;
5463     
5464   case MachinePlaysBlack:
5465   case MachinePlaysWhite:
5466     /* disable certain menu options while machine is thinking */
5467     SetMachineThinkingEnables();
5468     break;
5469
5470   default:
5471     break;
5472   }
5473
5474   if(bookHit) { // [HGM] book: simulate book reply
5475         static char bookMove[MSG_SIZ]; // a bit generous?
5476
5477         programStats.nodes = programStats.depth = programStats.time = 
5478         programStats.score = programStats.got_only_move = 0;
5479         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5480
5481         strcpy(bookMove, "move ");
5482         strcat(bookMove, bookHit);
5483         HandleMachineMove(bookMove, &first);
5484   }
5485   return 1;
5486 }
5487
5488 void
5489 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5490      int fromX, fromY, toX, toY;
5491      int promoChar;
5492 {
5493     /* [HGM] This routine was added to allow calling of its two logical
5494        parts from other modules in the old way. Before, UserMoveEvent()
5495        automatically called FinishMove() if the move was OK, and returned
5496        otherwise. I separated the two, in order to make it possible to
5497        slip a promotion popup in between. But that it always needs two
5498        calls, to the first part, (now called UserMoveTest() ), and to
5499        FinishMove if the first part succeeded. Calls that do not need
5500        to do anything in between, can call this routine the old way. 
5501     */
5502     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5503 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5504     if(moveType == AmbiguousMove)
5505         DrawPosition(FALSE, boards[currentMove]);
5506     else if(moveType != ImpossibleMove && moveType != Comment)
5507         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5508 }
5509
5510 void LeftClick(ClickType clickType, int xPix, int yPix)
5511 {
5512     int x, y;
5513     Boolean saveAnimate;
5514     static int second = 0, promotionChoice = 0;
5515     char promoChoice = NULLCHAR;
5516
5517     if (clickType == Press) ErrorPopDown();
5518
5519     x = EventToSquare(xPix, BOARD_WIDTH);
5520     y = EventToSquare(yPix, BOARD_HEIGHT);
5521     if (!flipView && y >= 0) {
5522         y = BOARD_HEIGHT - 1 - y;
5523     }
5524     if (flipView && x >= 0) {
5525         x = BOARD_WIDTH - 1 - x;
5526     }
5527
5528     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5529         if(clickType == Release) return; // ignore upclick of click-click destination
5530         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5531         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5532         if(gameInfo.holdingsWidth && 
5533                 (WhiteOnMove(currentMove) 
5534                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5535                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5536             // click in right holdings, for determining promotion piece
5537             ChessSquare p = boards[currentMove][y][x];
5538             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5539             if(p != EmptySquare) {
5540                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5541                 fromX = fromY = -1;
5542                 return;
5543             }
5544         }
5545         DrawPosition(FALSE, boards[currentMove]);
5546         return;
5547     }
5548
5549     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5550     if(clickType == Press
5551             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5552               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5553               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5554         return;
5555
5556     if (fromX == -1) {
5557         if (clickType == Press) {
5558             /* First square */
5559             if (OKToStartUserMove(x, y)) {
5560                 fromX = x;
5561                 fromY = y;
5562                 second = 0;
5563                 DragPieceBegin(xPix, yPix);
5564                 if (appData.highlightDragging) {
5565                     SetHighlights(x, y, -1, -1);
5566                 }
5567             }
5568         }
5569         return;
5570     }
5571
5572     /* fromX != -1 */
5573     if (clickType == Press && gameMode != EditPosition) {
5574         ChessSquare fromP;
5575         ChessSquare toP;
5576         int frc;
5577
5578         // ignore off-board to clicks
5579         if(y < 0 || x < 0) return;
5580
5581         /* Check if clicking again on the same color piece */
5582         fromP = boards[currentMove][fromY][fromX];
5583         toP = boards[currentMove][y][x];
5584         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5585         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5586              WhitePawn <= toP && toP <= WhiteKing &&
5587              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5588              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5589             (BlackPawn <= fromP && fromP <= BlackKing && 
5590              BlackPawn <= toP && toP <= BlackKing &&
5591              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5592              !(fromP == BlackKing && toP == BlackRook && frc))) {
5593             /* Clicked again on same color piece -- changed his mind */
5594             second = (x == fromX && y == fromY);
5595             if (appData.highlightDragging) {
5596                 SetHighlights(x, y, -1, -1);
5597             } else {
5598                 ClearHighlights();
5599             }
5600             if (OKToStartUserMove(x, y)) {
5601                 fromX = x;
5602                 fromY = y;
5603                 DragPieceBegin(xPix, yPix);
5604             }
5605             return;
5606         }
5607         // ignore to-clicks in holdings
5608         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5609     }
5610
5611     if (clickType == Release && (x == fromX && y == fromY ||
5612         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5613
5614         // treat drags into holding as click on start square
5615         x = fromX; y = fromY;
5616
5617         DragPieceEnd(xPix, yPix);
5618         if (appData.animateDragging) {
5619             /* Undo animation damage if any */
5620             DrawPosition(FALSE, NULL);
5621         }
5622         if (second) {
5623             /* Second up/down in same square; just abort move */
5624             second = 0;
5625             fromX = fromY = -1;
5626             ClearHighlights();
5627             gotPremove = 0;
5628             ClearPremoveHighlights();
5629         } else {
5630             /* First upclick in same square; start click-click mode */
5631             SetHighlights(x, y, -1, -1);
5632         }
5633         return;
5634     }
5635
5636     /* we now have a different from- and to-square */
5637     /* Completed move */
5638     toX = x;
5639     toY = y;
5640     saveAnimate = appData.animate;
5641     if (clickType == Press) {
5642         /* Finish clickclick move */
5643         if (appData.animate || appData.highlightLastMove) {
5644             SetHighlights(fromX, fromY, toX, toY);
5645         } else {
5646             ClearHighlights();
5647         }
5648     } else {
5649         /* Finish drag move */
5650         if (appData.highlightLastMove) {
5651             SetHighlights(fromX, fromY, toX, toY);
5652         } else {
5653             ClearHighlights();
5654         }
5655         DragPieceEnd(xPix, yPix);
5656         /* Don't animate move and drag both */
5657         appData.animate = FALSE;
5658     }
5659     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5660         SetHighlights(fromX, fromY, toX, toY);
5661         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5662             // [HGM] super: promotion to captured piece selected from holdings
5663             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5664             promotionChoice = TRUE;
5665             // kludge follows to temporarily execute move on display, without promoting yet
5666             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5667             boards[currentMove][toY][toX] = p;
5668             DrawPosition(FALSE, boards[currentMove]);
5669             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5670             boards[currentMove][toY][toX] = q;
5671             DisplayMessage("Click in holdings to choose piece", "");
5672             return;
5673         }
5674         PromotionPopUp();
5675     } else {
5676         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5677         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5678         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5679         fromX = fromY = -1;
5680     }
5681     appData.animate = saveAnimate;
5682     if (appData.animate || appData.animateDragging) {
5683         /* Undo animation damage if needed */
5684         DrawPosition(FALSE, NULL);
5685     }
5686 }
5687
5688 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5689 {
5690 //    char * hint = lastHint;
5691     FrontEndProgramStats stats;
5692
5693     stats.which = cps == &first ? 0 : 1;
5694     stats.depth = cpstats->depth;
5695     stats.nodes = cpstats->nodes;
5696     stats.score = cpstats->score;
5697     stats.time = cpstats->time;
5698     stats.pv = cpstats->movelist;
5699     stats.hint = lastHint;
5700     stats.an_move_index = 0;
5701     stats.an_move_count = 0;
5702
5703     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5704         stats.hint = cpstats->move_name;
5705         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5706         stats.an_move_count = cpstats->nr_moves;
5707     }
5708
5709     SetProgramStats( &stats );
5710 }
5711
5712 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5713 {   // [HGM] book: this routine intercepts moves to simulate book replies
5714     char *bookHit = NULL;
5715
5716     //first determine if the incoming move brings opponent into his book
5717     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5718         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5719     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5720     if(bookHit != NULL && !cps->bookSuspend) {
5721         // make sure opponent is not going to reply after receiving move to book position
5722         SendToProgram("force\n", cps);
5723         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5724     }
5725     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5726     // now arrange restart after book miss
5727     if(bookHit) {
5728         // after a book hit we never send 'go', and the code after the call to this routine
5729         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5730         char buf[MSG_SIZ];
5731         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5732         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5733         SendToProgram(buf, cps);
5734         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5735     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5736         SendToProgram("go\n", cps);
5737         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5738     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5739         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5740             SendToProgram("go\n", cps); 
5741         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5742     }
5743     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5744 }
5745
5746 char *savedMessage;
5747 ChessProgramState *savedState;
5748 void DeferredBookMove(void)
5749 {
5750         if(savedState->lastPing != savedState->lastPong)
5751                     ScheduleDelayedEvent(DeferredBookMove, 10);
5752         else
5753         HandleMachineMove(savedMessage, savedState);
5754 }
5755
5756 void
5757 HandleMachineMove(message, cps)
5758      char *message;
5759      ChessProgramState *cps;
5760 {
5761     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5762     char realname[MSG_SIZ];
5763     int fromX, fromY, toX, toY;
5764     ChessMove moveType;
5765     char promoChar;
5766     char *p;
5767     int machineWhite;
5768     char *bookHit;
5769
5770 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5771     /*
5772      * Kludge to ignore BEL characters
5773      */
5774     while (*message == '\007') message++;
5775
5776     /*
5777      * [HGM] engine debug message: ignore lines starting with '#' character
5778      */
5779     if(cps->debug && *message == '#') return;
5780
5781     /*
5782      * Look for book output
5783      */
5784     if (cps == &first && bookRequested) {
5785         if (message[0] == '\t' || message[0] == ' ') {
5786             /* Part of the book output is here; append it */
5787             strcat(bookOutput, message);
5788             strcat(bookOutput, "  \n");
5789             return;
5790         } else if (bookOutput[0] != NULLCHAR) {
5791             /* All of book output has arrived; display it */
5792             char *p = bookOutput;
5793             while (*p != NULLCHAR) {
5794                 if (*p == '\t') *p = ' ';
5795                 p++;
5796             }
5797             DisplayInformation(bookOutput);
5798             bookRequested = FALSE;
5799             /* Fall through to parse the current output */
5800         }
5801     }
5802
5803     /*
5804      * Look for machine move.
5805      */
5806     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5807         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5808     {
5809         /* This method is only useful on engines that support ping */
5810         if (cps->lastPing != cps->lastPong) {
5811           if (gameMode == BeginningOfGame) {
5812             /* Extra move from before last new; ignore */
5813             if (appData.debugMode) {
5814                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5815             }
5816           } else {
5817             if (appData.debugMode) {
5818                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5819                         cps->which, gameMode);
5820             }
5821
5822             SendToProgram("undo\n", cps);
5823           }
5824           return;
5825         }
5826
5827         switch (gameMode) {
5828           case BeginningOfGame:
5829             /* Extra move from before last reset; ignore */
5830             if (appData.debugMode) {
5831                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5832             }
5833             return;
5834
5835           case EndOfGame:
5836           case IcsIdle:
5837           default:
5838             /* Extra move after we tried to stop.  The mode test is
5839                not a reliable way of detecting this problem, but it's
5840                the best we can do on engines that don't support ping.
5841             */
5842             if (appData.debugMode) {
5843                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5844                         cps->which, gameMode);
5845             }
5846             SendToProgram("undo\n", cps);
5847             return;
5848
5849           case MachinePlaysWhite:
5850           case IcsPlayingWhite:
5851             machineWhite = TRUE;
5852             break;
5853
5854           case MachinePlaysBlack:
5855           case IcsPlayingBlack:
5856             machineWhite = FALSE;
5857             break;
5858
5859           case TwoMachinesPlay:
5860             machineWhite = (cps->twoMachinesColor[0] == 'w');
5861             break;
5862         }
5863         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5864             if (appData.debugMode) {
5865                 fprintf(debugFP,
5866                         "Ignoring move out of turn by %s, gameMode %d"
5867                         ", forwardMost %d\n",
5868                         cps->which, gameMode, forwardMostMove);
5869             }
5870             return;
5871         }
5872
5873     if (appData.debugMode) { int f = forwardMostMove;
5874         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5875                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5876     }
5877         if(cps->alphaRank) AlphaRank(machineMove, 4);
5878         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5879                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5880             /* Machine move could not be parsed; ignore it. */
5881             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5882                     machineMove, cps->which);
5883             DisplayError(buf1, 0);
5884             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5885                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5886             if (gameMode == TwoMachinesPlay) {
5887               GameEnds(machineWhite ? BlackWins : WhiteWins,
5888                        buf1, GE_XBOARD);
5889             }
5890             return;
5891         }
5892
5893         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5894         /* So we have to redo legality test with true e.p. status here,  */
5895         /* to make sure an illegal e.p. capture does not slip through,   */
5896         /* to cause a forfeit on a justified illegal-move complaint      */
5897         /* of the opponent.                                              */
5898         if( gameMode==TwoMachinesPlay && appData.testLegality
5899             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5900                                                               ) {
5901            ChessMove moveType;
5902            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5903                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5904                              fromY, fromX, toY, toX, promoChar);
5905             if (appData.debugMode) {
5906                 int i;
5907                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5908                     castlingRights[forwardMostMove][i], castlingRank[i]);
5909                 fprintf(debugFP, "castling rights\n");
5910             }
5911             if(moveType == IllegalMove) {
5912                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5913                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5914                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5915                            buf1, GE_XBOARD);
5916                 return;
5917            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5918            /* [HGM] Kludge to handle engines that send FRC-style castling
5919               when they shouldn't (like TSCP-Gothic) */
5920            switch(moveType) {
5921              case WhiteASideCastleFR:
5922              case BlackASideCastleFR:
5923                toX+=2;
5924                currentMoveString[2]++;
5925                break;
5926              case WhiteHSideCastleFR:
5927              case BlackHSideCastleFR:
5928                toX--;
5929                currentMoveString[2]--;
5930                break;
5931              default: ; // nothing to do, but suppresses warning of pedantic compilers
5932            }
5933         }
5934         hintRequested = FALSE;
5935         lastHint[0] = NULLCHAR;
5936         bookRequested = FALSE;
5937         /* Program may be pondering now */
5938         cps->maybeThinking = TRUE;
5939         if (cps->sendTime == 2) cps->sendTime = 1;
5940         if (cps->offeredDraw) cps->offeredDraw--;
5941
5942 #if ZIPPY
5943         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5944             first.initDone) {
5945           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5946           ics_user_moved = 1;
5947           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5948                 char buf[3*MSG_SIZ];
5949
5950                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5951                         programStats.score / 100.,
5952                         programStats.depth,
5953                         programStats.time / 100.,
5954                         (unsigned int)programStats.nodes,
5955                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5956                         programStats.movelist);
5957                 SendToICS(buf);
5958 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5959           }
5960         }
5961 #endif
5962         /* currentMoveString is set as a side-effect of ParseOneMove */
5963         strcpy(machineMove, currentMoveString);
5964         strcat(machineMove, "\n");
5965         strcpy(moveList[forwardMostMove], machineMove);
5966
5967         /* [AS] Save move info and clear stats for next move */
5968         pvInfoList[ forwardMostMove ].score = programStats.score;
5969         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5970         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5971         ClearProgramStats();
5972         thinkOutput[0] = NULLCHAR;
5973         hiddenThinkOutputState = 0;
5974
5975         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5976
5977         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5978         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5979             int count = 0;
5980
5981             while( count < adjudicateLossPlies ) {
5982                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5983
5984                 if( count & 1 ) {
5985                     score = -score; /* Flip score for winning side */
5986                 }
5987
5988                 if( score > adjudicateLossThreshold ) {
5989                     break;
5990                 }
5991
5992                 count++;
5993             }
5994
5995             if( count >= adjudicateLossPlies ) {
5996                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5997
5998                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5999                     "Xboard adjudication", 
6000                     GE_XBOARD );
6001
6002                 return;
6003             }
6004         }
6005
6006         if( gameMode == TwoMachinesPlay ) {
6007           // [HGM] some adjudications useful with buggy engines
6008             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6009           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6010
6011
6012             if( appData.testLegality )
6013             {   /* [HGM] Some more adjudications for obstinate engines */
6014                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6015                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6016                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6017                 static int moveCount = 6;
6018                 ChessMove result;
6019                 char *reason = NULL;
6020
6021                 /* Count what is on board. */
6022                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6023                 {   ChessSquare p = boards[forwardMostMove][i][j];
6024                     int m=i;
6025
6026                     switch((int) p)
6027                     {   /* count B,N,R and other of each side */
6028                         case WhiteKing:
6029                         case BlackKing:
6030                              NrK++; break; // [HGM] atomic: count Kings
6031                         case WhiteKnight:
6032                              NrWN++; break;
6033                         case WhiteBishop:
6034                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6035                              bishopsColor |= 1 << ((i^j)&1);
6036                              NrWB++; break;
6037                         case BlackKnight:
6038                              NrBN++; break;
6039                         case BlackBishop:
6040                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6041                              bishopsColor |= 1 << ((i^j)&1);
6042                              NrBB++; break;
6043                         case WhiteRook:
6044                              NrWR++; break;
6045                         case BlackRook:
6046                              NrBR++; break;
6047                         case WhiteQueen:
6048                              NrWQ++; break;
6049                         case BlackQueen:
6050                              NrBQ++; break;
6051                         case EmptySquare: 
6052                              break;
6053                         case BlackPawn:
6054                              m = 7-i;
6055                         case WhitePawn:
6056                              PawnAdvance += m; NrPawns++;
6057                     }
6058                     NrPieces += (p != EmptySquare);
6059                     NrW += ((int)p < (int)BlackPawn);
6060                     if(gameInfo.variant == VariantXiangqi && 
6061                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6062                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6063                         NrW -= ((int)p < (int)BlackPawn);
6064                     }
6065                 }
6066
6067                 /* Some material-based adjudications that have to be made before stalemate test */
6068                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6069                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6070                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6071                      if(appData.checkMates) {
6072                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6073                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6074                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6075                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6076                          return;
6077                      }
6078                 }
6079
6080                 /* Bare King in Shatranj (loses) or Losers (wins) */
6081                 if( NrW == 1 || NrPieces - NrW == 1) {
6082                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6083                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6084                      if(appData.checkMates) {
6085                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6086                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6087                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6088                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6089                          return;
6090                      }
6091                   } else
6092                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6093                   {    /* bare King */
6094                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6095                         if(appData.checkMates) {
6096                             /* but only adjudicate if adjudication enabled */
6097                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6098                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6100                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6101                             return;
6102                         }
6103                   }
6104                 } else bare = 1;
6105
6106
6107             // don't wait for engine to announce game end if we can judge ourselves
6108             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6109                                        castlingRights[forwardMostMove]) ) {
6110               case MT_CHECK:
6111                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6112                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6113                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6114                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6115                             checkCnt++;
6116                         if(checkCnt >= 2) {
6117                             reason = "Xboard adjudication: 3rd check";
6118                             epStatus[forwardMostMove] = EP_CHECKMATE;
6119                             break;
6120                         }
6121                     }
6122                 }
6123               case MT_NONE:
6124               default:
6125                 break;
6126               case MT_STALEMATE:
6127               case MT_STAINMATE:
6128                 reason = "Xboard adjudication: Stalemate";
6129                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6130                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6131                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6132                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6133                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6134                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6135                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6136                                                                         EP_CHECKMATE : EP_WINS);
6137                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6138                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6139                 }
6140                 break;
6141               case MT_CHECKMATE:
6142                 reason = "Xboard adjudication: Checkmate";
6143                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6144                 break;
6145             }
6146
6147                 switch(i = epStatus[forwardMostMove]) {
6148                     case EP_STALEMATE:
6149                         result = GameIsDrawn; break;
6150                     case EP_CHECKMATE:
6151                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6152                     case EP_WINS:
6153                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6154                     default:
6155                         result = (ChessMove) 0;
6156                 }
6157                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6158                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6159                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6160                     GameEnds( result, reason, GE_XBOARD );
6161                     return;
6162                 }
6163
6164                 /* Next absolutely insufficient mating material. */
6165                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6166                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6167                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6168                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6169                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6170
6171                      /* always flag draws, for judging claims */
6172                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6173
6174                      if(appData.materialDraws) {
6175                          /* but only adjudicate them if adjudication enabled */
6176                          SendToProgram("force\n", cps->other); // suppress reply
6177                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6178                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6179                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6180                          return;
6181                      }
6182                 }
6183
6184                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6185                 if(NrPieces == 4 && 
6186                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6187                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6188                    || NrWN==2 || NrBN==2     /* KNNK */
6189                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6190                   ) ) {
6191                      if(--moveCount < 0 && appData.trivialDraws)
6192                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6193                           SendToProgram("force\n", cps->other); // suppress reply
6194                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6195                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6196                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6197                           return;
6198                      }
6199                 } else moveCount = 6;
6200             }
6201           }
6202           
6203           if (appData.debugMode) { int i;
6204             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6205                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6206                     appData.drawRepeats);
6207             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6208               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6209             
6210           }
6211
6212                 /* Check for rep-draws */
6213                 count = 0;
6214                 for(k = forwardMostMove-2;
6215                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6216                         epStatus[k] < EP_UNKNOWN &&
6217                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6218                     k-=2)
6219                 {   int rights=0;
6220                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6221                         /* compare castling rights */
6222                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6223                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6224                                 rights++; /* King lost rights, while rook still had them */
6225                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6226                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6227                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6228                                    rights++; /* but at least one rook lost them */
6229                         }
6230                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6231                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6232                                 rights++; 
6233                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6234                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6235                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6236                                    rights++;
6237                         }
6238                         if( rights == 0 && ++count > appData.drawRepeats-2
6239                             && appData.drawRepeats > 1) {
6240                              /* adjudicate after user-specified nr of repeats */
6241                              SendToProgram("force\n", cps->other); // suppress reply
6242                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6243                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6244                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6245                                 // [HGM] xiangqi: check for forbidden perpetuals
6246                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6247                                 for(m=forwardMostMove; m>k; m-=2) {
6248                                     if(MateTest(boards[m], PosFlags(m), 
6249                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6250                                         ourPerpetual = 0; // the current mover did not always check
6251                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6252                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6253                                         hisPerpetual = 0; // the opponent did not always check
6254                                 }
6255                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6256                                                                         ourPerpetual, hisPerpetual);
6257                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6258                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6259                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6260                                     return;
6261                                 }
6262                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6263                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6264                                 // Now check for perpetual chases
6265                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6266                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6267                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6268                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6269                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6270                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6271                                         return;
6272                                     }
6273                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6274                                         break; // Abort repetition-checking loop.
6275                                 }
6276                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6277                              }
6278                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6279                              return;
6280                         }
6281                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6282                              epStatus[forwardMostMove] = EP_REP_DRAW;
6283                     }
6284                 }
6285
6286                 /* Now we test for 50-move draws. Determine ply count */
6287                 count = forwardMostMove;
6288                 /* look for last irreversble move */
6289                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6290                     count--;
6291                 /* if we hit starting position, add initial plies */
6292                 if( count == backwardMostMove )
6293                     count -= initialRulePlies;
6294                 count = forwardMostMove - count; 
6295                 if( count >= 100)
6296                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6297                          /* this is used to judge if draw claims are legal */
6298                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6299                          SendToProgram("force\n", cps->other); // suppress reply
6300                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6301                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6302                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6303                          return;
6304                 }
6305
6306                 /* if draw offer is pending, treat it as a draw claim
6307                  * when draw condition present, to allow engines a way to
6308                  * claim draws before making their move to avoid a race
6309                  * condition occurring after their move
6310                  */
6311                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6312                          char *p = NULL;
6313                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6314                              p = "Draw claim: 50-move rule";
6315                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6316                              p = "Draw claim: 3-fold repetition";
6317                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6318                              p = "Draw claim: insufficient mating material";
6319                          if( p != NULL ) {
6320                              SendToProgram("force\n", cps->other); // suppress reply
6321                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6322                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6323                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6324                              return;
6325                          }
6326                 }
6327
6328
6329                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6330                     SendToProgram("force\n", cps->other); // suppress reply
6331                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6332                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6333
6334                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6335
6336                     return;
6337                 }
6338         }
6339
6340         bookHit = NULL;
6341         if (gameMode == TwoMachinesPlay) {
6342             /* [HGM] relaying draw offers moved to after reception of move */
6343             /* and interpreting offer as claim if it brings draw condition */
6344             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6345                 SendToProgram("draw\n", cps->other);
6346             }
6347             if (cps->other->sendTime) {
6348                 SendTimeRemaining(cps->other,
6349                                   cps->other->twoMachinesColor[0] == 'w');
6350             }
6351             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6352             if (firstMove && !bookHit) {
6353                 firstMove = FALSE;
6354                 if (cps->other->useColors) {
6355                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6356                 }
6357                 SendToProgram("go\n", cps->other);
6358             }
6359             cps->other->maybeThinking = TRUE;
6360         }
6361
6362         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6363         
6364         if (!pausing && appData.ringBellAfterMoves) {
6365             RingBell();
6366         }
6367
6368         /* 
6369          * Reenable menu items that were disabled while
6370          * machine was thinking
6371          */
6372         if (gameMode != TwoMachinesPlay)
6373             SetUserThinkingEnables();
6374
6375         // [HGM] book: after book hit opponent has received move and is now in force mode
6376         // force the book reply into it, and then fake that it outputted this move by jumping
6377         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6378         if(bookHit) {
6379                 static char bookMove[MSG_SIZ]; // a bit generous?
6380
6381                 strcpy(bookMove, "move ");
6382                 strcat(bookMove, bookHit);
6383                 message = bookMove;
6384                 cps = cps->other;
6385                 programStats.nodes = programStats.depth = programStats.time = 
6386                 programStats.score = programStats.got_only_move = 0;
6387                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6388
6389                 if(cps->lastPing != cps->lastPong) {
6390                     savedMessage = message; // args for deferred call
6391                     savedState = cps;
6392                     ScheduleDelayedEvent(DeferredBookMove, 10);
6393                     return;
6394                 }
6395                 goto FakeBookMove;
6396         }
6397
6398         return;
6399     }
6400
6401     /* Set special modes for chess engines.  Later something general
6402      *  could be added here; for now there is just one kludge feature,
6403      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6404      *  when "xboard" is given as an interactive command.
6405      */
6406     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6407         cps->useSigint = FALSE;
6408         cps->useSigterm = FALSE;
6409     }
6410     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6411       ParseFeatures(message+8, cps);
6412       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6413     }
6414
6415     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6416      * want this, I was asked to put it in, and obliged.
6417      */
6418     if (!strncmp(message, "setboard ", 9)) {
6419         Board initial_position; int i;
6420
6421         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6422
6423         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6424             DisplayError(_("Bad FEN received from engine"), 0);
6425             return ;
6426         } else {
6427            Reset(FALSE, FALSE);
6428            CopyBoard(boards[0], initial_position);
6429            initialRulePlies = FENrulePlies;
6430            epStatus[0] = FENepStatus;
6431            for( i=0; i<nrCastlingRights; i++ )
6432                 castlingRights[0][i] = FENcastlingRights[i];
6433            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6434            else gameMode = MachinePlaysBlack;                 
6435            DrawPosition(FALSE, boards[currentMove]);
6436         }
6437         return;
6438     }
6439
6440     /*
6441      * Look for communication commands
6442      */
6443     if (!strncmp(message, "telluser ", 9)) {
6444         DisplayNote(message + 9);
6445         return;
6446     }
6447     if (!strncmp(message, "tellusererror ", 14)) {
6448         DisplayError(message + 14, 0);
6449         return;
6450     }
6451     if (!strncmp(message, "tellopponent ", 13)) {
6452       if (appData.icsActive) {
6453         if (loggedOn) {
6454           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6455           SendToICS(buf1);
6456         }
6457       } else {
6458         DisplayNote(message + 13);
6459       }
6460       return;
6461     }
6462     if (!strncmp(message, "tellothers ", 11)) {
6463       if (appData.icsActive) {
6464         if (loggedOn) {
6465           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6466           SendToICS(buf1);
6467         }
6468       }
6469       return;
6470     }
6471     if (!strncmp(message, "tellall ", 8)) {
6472       if (appData.icsActive) {
6473         if (loggedOn) {
6474           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6475           SendToICS(buf1);
6476         }
6477       } else {
6478         DisplayNote(message + 8);
6479       }
6480       return;
6481     }
6482     if (strncmp(message, "warning", 7) == 0) {
6483         /* Undocumented feature, use tellusererror in new code */
6484         DisplayError(message, 0);
6485         return;
6486     }
6487     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6488         strcpy(realname, cps->tidy);
6489         strcat(realname, " query");
6490         AskQuestion(realname, buf2, buf1, cps->pr);
6491         return;
6492     }
6493     /* Commands from the engine directly to ICS.  We don't allow these to be 
6494      *  sent until we are logged on. Crafty kibitzes have been known to 
6495      *  interfere with the login process.
6496      */
6497     if (loggedOn) {
6498         if (!strncmp(message, "tellics ", 8)) {
6499             SendToICS(message + 8);
6500             SendToICS("\n");
6501             return;
6502         }
6503         if (!strncmp(message, "tellicsnoalias ", 15)) {
6504             SendToICS(ics_prefix);
6505             SendToICS(message + 15);
6506             SendToICS("\n");
6507             return;
6508         }
6509         /* The following are for backward compatibility only */
6510         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6511             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6512             SendToICS(ics_prefix);
6513             SendToICS(message);
6514             SendToICS("\n");
6515             return;
6516         }
6517     }
6518     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6519         return;
6520     }
6521     /*
6522      * If the move is illegal, cancel it and redraw the board.
6523      * Also deal with other error cases.  Matching is rather loose
6524      * here to accommodate engines written before the spec.
6525      */
6526     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6527         strncmp(message, "Error", 5) == 0) {
6528         if (StrStr(message, "name") || 
6529             StrStr(message, "rating") || StrStr(message, "?") ||
6530             StrStr(message, "result") || StrStr(message, "board") ||
6531             StrStr(message, "bk") || StrStr(message, "computer") ||
6532             StrStr(message, "variant") || StrStr(message, "hint") ||
6533             StrStr(message, "random") || StrStr(message, "depth") ||
6534             StrStr(message, "accepted")) {
6535             return;
6536         }
6537         if (StrStr(message, "protover")) {
6538           /* Program is responding to input, so it's apparently done
6539              initializing, and this error message indicates it is
6540              protocol version 1.  So we don't need to wait any longer
6541              for it to initialize and send feature commands. */
6542           FeatureDone(cps, 1);
6543           cps->protocolVersion = 1;
6544           return;
6545         }
6546         cps->maybeThinking = FALSE;
6547
6548         if (StrStr(message, "draw")) {
6549             /* Program doesn't have "draw" command */
6550             cps->sendDrawOffers = 0;
6551             return;
6552         }
6553         if (cps->sendTime != 1 &&
6554             (StrStr(message, "time") || StrStr(message, "otim"))) {
6555           /* Program apparently doesn't have "time" or "otim" command */
6556           cps->sendTime = 0;
6557           return;
6558         }
6559         if (StrStr(message, "analyze")) {
6560             cps->analysisSupport = FALSE;
6561             cps->analyzing = FALSE;
6562             Reset(FALSE, TRUE);
6563             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6564             DisplayError(buf2, 0);
6565             return;
6566         }
6567         if (StrStr(message, "(no matching move)st")) {
6568           /* Special kludge for GNU Chess 4 only */
6569           cps->stKludge = TRUE;
6570           SendTimeControl(cps, movesPerSession, timeControl,
6571                           timeIncrement, appData.searchDepth,
6572                           searchTime);
6573           return;
6574         }
6575         if (StrStr(message, "(no matching move)sd")) {
6576           /* Special kludge for GNU Chess 4 only */
6577           cps->sdKludge = TRUE;
6578           SendTimeControl(cps, movesPerSession, timeControl,
6579                           timeIncrement, appData.searchDepth,
6580                           searchTime);
6581           return;
6582         }
6583         if (!StrStr(message, "llegal")) {
6584             return;
6585         }
6586         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6587             gameMode == IcsIdle) return;
6588         if (forwardMostMove <= backwardMostMove) return;
6589         if (pausing) PauseEvent();
6590       if(appData.forceIllegal) {
6591             // [HGM] illegal: machine refused move; force position after move into it
6592           SendToProgram("force\n", cps);
6593           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6594                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6595                 // when black is to move, while there might be nothing on a2 or black
6596                 // might already have the move. So send the board as if white has the move.
6597                 // But first we must change the stm of the engine, as it refused the last move
6598                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6599                 if(WhiteOnMove(forwardMostMove)) {
6600                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6601                     SendBoard(cps, forwardMostMove); // kludgeless board
6602                 } else {
6603                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6604                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6605                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6606                 }
6607           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6608             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6609                  gameMode == TwoMachinesPlay)
6610               SendToProgram("go\n", cps);
6611             return;
6612       } else
6613         if (gameMode == PlayFromGameFile) {
6614             /* Stop reading this game file */
6615             gameMode = EditGame;
6616             ModeHighlight();
6617         }
6618         currentMove = --forwardMostMove;
6619         DisplayMove(currentMove-1); /* before DisplayMoveError */
6620         SwitchClocks();
6621         DisplayBothClocks();
6622         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6623                 parseList[currentMove], cps->which);
6624         DisplayMoveError(buf1);
6625         DrawPosition(FALSE, boards[currentMove]);
6626
6627         /* [HGM] illegal-move claim should forfeit game when Xboard */
6628         /* only passes fully legal moves                            */
6629         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6630             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6631                                 "False illegal-move claim", GE_XBOARD );
6632         }
6633         return;
6634     }
6635     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6636         /* Program has a broken "time" command that
6637            outputs a string not ending in newline.
6638            Don't use it. */
6639         cps->sendTime = 0;
6640     }
6641     
6642     /*
6643      * If chess program startup fails, exit with an error message.
6644      * Attempts to recover here are futile.
6645      */
6646     if ((StrStr(message, "unknown host") != NULL)
6647         || (StrStr(message, "No remote directory") != NULL)
6648         || (StrStr(message, "not found") != NULL)
6649         || (StrStr(message, "No such file") != NULL)
6650         || (StrStr(message, "can't alloc") != NULL)
6651         || (StrStr(message, "Permission denied") != NULL)) {
6652
6653         cps->maybeThinking = FALSE;
6654         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6655                 cps->which, cps->program, cps->host, message);
6656         RemoveInputSource(cps->isr);
6657         DisplayFatalError(buf1, 0, 1);
6658         return;
6659     }
6660     
6661     /* 
6662      * Look for hint output
6663      */
6664     if (sscanf(message, "Hint: %s", buf1) == 1) {
6665         if (cps == &first && hintRequested) {
6666             hintRequested = FALSE;
6667             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6668                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6669                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6670                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6671                                     fromY, fromX, toY, toX, promoChar, buf1);
6672                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6673                 DisplayInformation(buf2);
6674             } else {
6675                 /* Hint move could not be parsed!? */
6676               snprintf(buf2, sizeof(buf2),
6677                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6678                         buf1, cps->which);
6679                 DisplayError(buf2, 0);
6680             }
6681         } else {
6682             strcpy(lastHint, buf1);
6683         }
6684         return;
6685     }
6686
6687     /*
6688      * Ignore other messages if game is not in progress
6689      */
6690     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6691         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6692
6693     /*
6694      * look for win, lose, draw, or draw offer
6695      */
6696     if (strncmp(message, "1-0", 3) == 0) {
6697         char *p, *q, *r = "";
6698         p = strchr(message, '{');
6699         if (p) {
6700             q = strchr(p, '}');
6701             if (q) {
6702                 *q = NULLCHAR;
6703                 r = p + 1;
6704             }
6705         }
6706         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6707         return;
6708     } else if (strncmp(message, "0-1", 3) == 0) {
6709         char *p, *q, *r = "";
6710         p = strchr(message, '{');
6711         if (p) {
6712             q = strchr(p, '}');
6713             if (q) {
6714                 *q = NULLCHAR;
6715                 r = p + 1;
6716             }
6717         }
6718         /* Kludge for Arasan 4.1 bug */
6719         if (strcmp(r, "Black resigns") == 0) {
6720             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6721             return;
6722         }
6723         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6724         return;
6725     } else if (strncmp(message, "1/2", 3) == 0) {
6726         char *p, *q, *r = "";
6727         p = strchr(message, '{');
6728         if (p) {
6729             q = strchr(p, '}');
6730             if (q) {
6731                 *q = NULLCHAR;
6732                 r = p + 1;
6733             }
6734         }
6735             
6736         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6737         return;
6738
6739     } else if (strncmp(message, "White resign", 12) == 0) {
6740         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6741         return;
6742     } else if (strncmp(message, "Black resign", 12) == 0) {
6743         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6744         return;
6745     } else if (strncmp(message, "White matches", 13) == 0 ||
6746                strncmp(message, "Black matches", 13) == 0   ) {
6747         /* [HGM] ignore GNUShogi noises */
6748         return;
6749     } else if (strncmp(message, "White", 5) == 0 &&
6750                message[5] != '(' &&
6751                StrStr(message, "Black") == NULL) {
6752         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6753         return;
6754     } else if (strncmp(message, "Black", 5) == 0 &&
6755                message[5] != '(') {
6756         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6757         return;
6758     } else if (strcmp(message, "resign") == 0 ||
6759                strcmp(message, "computer resigns") == 0) {
6760         switch (gameMode) {
6761           case MachinePlaysBlack:
6762           case IcsPlayingBlack:
6763             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6764             break;
6765           case MachinePlaysWhite:
6766           case IcsPlayingWhite:
6767             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6768             break;
6769           case TwoMachinesPlay:
6770             if (cps->twoMachinesColor[0] == 'w')
6771               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6772             else
6773               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6774             break;
6775           default:
6776             /* can't happen */
6777             break;
6778         }
6779         return;
6780     } else if (strncmp(message, "opponent mates", 14) == 0) {
6781         switch (gameMode) {
6782           case MachinePlaysBlack:
6783           case IcsPlayingBlack:
6784             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6785             break;
6786           case MachinePlaysWhite:
6787           case IcsPlayingWhite:
6788             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6789             break;
6790           case TwoMachinesPlay:
6791             if (cps->twoMachinesColor[0] == 'w')
6792               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6793             else
6794               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6795             break;
6796           default:
6797             /* can't happen */
6798             break;
6799         }
6800         return;
6801     } else if (strncmp(message, "computer mates", 14) == 0) {
6802         switch (gameMode) {
6803           case MachinePlaysBlack:
6804           case IcsPlayingBlack:
6805             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6806             break;
6807           case MachinePlaysWhite:
6808           case IcsPlayingWhite:
6809             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6810             break;
6811           case TwoMachinesPlay:
6812             if (cps->twoMachinesColor[0] == 'w')
6813               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6814             else
6815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6816             break;
6817           default:
6818             /* can't happen */
6819             break;
6820         }
6821         return;
6822     } else if (strncmp(message, "checkmate", 9) == 0) {
6823         if (WhiteOnMove(forwardMostMove)) {
6824             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6825         } else {
6826             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6827         }
6828         return;
6829     } else if (strstr(message, "Draw") != NULL ||
6830                strstr(message, "game is a draw") != NULL) {
6831         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6832         return;
6833     } else if (strstr(message, "offer") != NULL &&
6834                strstr(message, "draw") != NULL) {
6835 #if ZIPPY
6836         if (appData.zippyPlay && first.initDone) {
6837             /* Relay offer to ICS */
6838             SendToICS(ics_prefix);
6839             SendToICS("draw\n");
6840         }
6841 #endif
6842         cps->offeredDraw = 2; /* valid until this engine moves twice */
6843         if (gameMode == TwoMachinesPlay) {
6844             if (cps->other->offeredDraw) {
6845                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6846             /* [HGM] in two-machine mode we delay relaying draw offer      */
6847             /* until after we also have move, to see if it is really claim */
6848             }
6849         } else if (gameMode == MachinePlaysWhite ||
6850                    gameMode == MachinePlaysBlack) {
6851           if (userOfferedDraw) {
6852             DisplayInformation(_("Machine accepts your draw offer"));
6853             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6854           } else {
6855             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6856           }
6857         }
6858     }
6859
6860     
6861     /*
6862      * Look for thinking output
6863      */
6864     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6865           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6866                                 ) {
6867         int plylev, mvleft, mvtot, curscore, time;
6868         char mvname[MOVE_LEN];
6869         u64 nodes; // [DM]
6870         char plyext;
6871         int ignore = FALSE;
6872         int prefixHint = FALSE;
6873         mvname[0] = NULLCHAR;
6874
6875         switch (gameMode) {
6876           case MachinePlaysBlack:
6877           case IcsPlayingBlack:
6878             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6879             break;
6880           case MachinePlaysWhite:
6881           case IcsPlayingWhite:
6882             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6883             break;
6884           case AnalyzeMode:
6885           case AnalyzeFile:
6886             break;
6887           case IcsObserving: /* [DM] icsEngineAnalyze */
6888             if (!appData.icsEngineAnalyze) ignore = TRUE;
6889             break;
6890           case TwoMachinesPlay:
6891             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6892                 ignore = TRUE;
6893             }
6894             break;
6895           default:
6896             ignore = TRUE;
6897             break;
6898         }
6899
6900         if (!ignore) {
6901             buf1[0] = NULLCHAR;
6902             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6903                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6904
6905                 if (plyext != ' ' && plyext != '\t') {
6906                     time *= 100;
6907                 }
6908
6909                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6910                 if( cps->scoreIsAbsolute && 
6911                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6912                 {
6913                     curscore = -curscore;
6914                 }
6915
6916
6917                 programStats.depth = plylev;
6918                 programStats.nodes = nodes;
6919                 programStats.time = time;
6920                 programStats.score = curscore;
6921                 programStats.got_only_move = 0;
6922
6923                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6924                         int ticklen;
6925
6926                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6927                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6928                         if(WhiteOnMove(forwardMostMove)) 
6929                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6930                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6931                 }
6932
6933                 /* Buffer overflow protection */
6934                 if (buf1[0] != NULLCHAR) {
6935                     if (strlen(buf1) >= sizeof(programStats.movelist)
6936                         && appData.debugMode) {
6937                         fprintf(debugFP,
6938                                 "PV is too long; using the first %d bytes.\n",
6939                                 sizeof(programStats.movelist) - 1);
6940                     }
6941
6942                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6943                 } else {
6944                     sprintf(programStats.movelist, " no PV\n");
6945                 }
6946
6947                 if (programStats.seen_stat) {
6948                     programStats.ok_to_send = 1;
6949                 }
6950
6951                 if (strchr(programStats.movelist, '(') != NULL) {
6952                     programStats.line_is_book = 1;
6953                     programStats.nr_moves = 0;
6954                     programStats.moves_left = 0;
6955                 } else {
6956                     programStats.line_is_book = 0;
6957                 }
6958
6959                 SendProgramStatsToFrontend( cps, &programStats );
6960
6961                 /* 
6962                     [AS] Protect the thinkOutput buffer from overflow... this
6963                     is only useful if buf1 hasn't overflowed first!
6964                 */
6965                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6966                         plylev, 
6967                         (gameMode == TwoMachinesPlay ?
6968                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6969                         ((double) curscore) / 100.0,
6970                         prefixHint ? lastHint : "",
6971                         prefixHint ? " " : "" );
6972
6973                 if( buf1[0] != NULLCHAR ) {
6974                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6975
6976                     if( strlen(buf1) > max_len ) {
6977                         if( appData.debugMode) {
6978                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6979                         }
6980                         buf1[max_len+1] = '\0';
6981                     }
6982
6983                     strcat( thinkOutput, buf1 );
6984                 }
6985
6986                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6987                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6988                     DisplayMove(currentMove - 1);
6989                 }
6990                 return;
6991
6992             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6993                 /* crafty (9.25+) says "(only move) <move>"
6994                  * if there is only 1 legal move
6995                  */
6996                 sscanf(p, "(only move) %s", buf1);
6997                 sprintf(thinkOutput, "%s (only move)", buf1);
6998                 sprintf(programStats.movelist, "%s (only move)", buf1);
6999                 programStats.depth = 1;
7000                 programStats.nr_moves = 1;
7001                 programStats.moves_left = 1;
7002                 programStats.nodes = 1;
7003                 programStats.time = 1;
7004                 programStats.got_only_move = 1;
7005
7006                 /* Not really, but we also use this member to
7007                    mean "line isn't going to change" (Crafty
7008                    isn't searching, so stats won't change) */
7009                 programStats.line_is_book = 1;
7010
7011                 SendProgramStatsToFrontend( cps, &programStats );
7012                 
7013                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7014                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7015                     DisplayMove(currentMove - 1);
7016                 }
7017                 return;
7018             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7019                               &time, &nodes, &plylev, &mvleft,
7020                               &mvtot, mvname) >= 5) {
7021                 /* The stat01: line is from Crafty (9.29+) in response
7022                    to the "." command */
7023                 programStats.seen_stat = 1;
7024                 cps->maybeThinking = TRUE;
7025
7026                 if (programStats.got_only_move || !appData.periodicUpdates)
7027                   return;
7028
7029                 programStats.depth = plylev;
7030                 programStats.time = time;
7031                 programStats.nodes = nodes;
7032                 programStats.moves_left = mvleft;
7033                 programStats.nr_moves = mvtot;
7034                 strcpy(programStats.move_name, mvname);
7035                 programStats.ok_to_send = 1;
7036                 programStats.movelist[0] = '\0';
7037
7038                 SendProgramStatsToFrontend( cps, &programStats );
7039
7040                 return;
7041
7042             } else if (strncmp(message,"++",2) == 0) {
7043                 /* Crafty 9.29+ outputs this */
7044                 programStats.got_fail = 2;
7045                 return;
7046
7047             } else if (strncmp(message,"--",2) == 0) {
7048                 /* Crafty 9.29+ outputs this */
7049                 programStats.got_fail = 1;
7050                 return;
7051
7052             } else if (thinkOutput[0] != NULLCHAR &&
7053                        strncmp(message, "    ", 4) == 0) {
7054                 unsigned message_len;
7055
7056                 p = message;
7057                 while (*p && *p == ' ') p++;
7058
7059                 message_len = strlen( p );
7060
7061                 /* [AS] Avoid buffer overflow */
7062                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7063                     strcat(thinkOutput, " ");
7064                     strcat(thinkOutput, p);
7065                 }
7066
7067                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7068                     strcat(programStats.movelist, " ");
7069                     strcat(programStats.movelist, p);
7070                 }
7071
7072                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7073                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7074                     DisplayMove(currentMove - 1);
7075                 }
7076                 return;
7077             }
7078         }
7079         else {
7080             buf1[0] = NULLCHAR;
7081
7082             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7083                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7084             {
7085                 ChessProgramStats cpstats;
7086
7087                 if (plyext != ' ' && plyext != '\t') {
7088                     time *= 100;
7089                 }
7090
7091                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7092                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7093                     curscore = -curscore;
7094                 }
7095
7096                 cpstats.depth = plylev;
7097                 cpstats.nodes = nodes;
7098                 cpstats.time = time;
7099                 cpstats.score = curscore;
7100                 cpstats.got_only_move = 0;
7101                 cpstats.movelist[0] = '\0';
7102
7103                 if (buf1[0] != NULLCHAR) {
7104                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7105                 }
7106
7107                 cpstats.ok_to_send = 0;
7108                 cpstats.line_is_book = 0;
7109                 cpstats.nr_moves = 0;
7110                 cpstats.moves_left = 0;
7111
7112                 SendProgramStatsToFrontend( cps, &cpstats );
7113             }
7114         }
7115     }
7116 }
7117
7118
7119 /* Parse a game score from the character string "game", and
7120    record it as the history of the current game.  The game
7121    score is NOT assumed to start from the standard position. 
7122    The display is not updated in any way.
7123    */
7124 void
7125 ParseGameHistory(game)
7126      char *game;
7127 {
7128     ChessMove moveType;
7129     int fromX, fromY, toX, toY, boardIndex;
7130     char promoChar;
7131     char *p, *q;
7132     char buf[MSG_SIZ];
7133
7134     if (appData.debugMode)
7135       fprintf(debugFP, "Parsing game history: %s\n", game);
7136
7137     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7138     gameInfo.site = StrSave(appData.icsHost);
7139     gameInfo.date = PGNDate();
7140     gameInfo.round = StrSave("-");
7141
7142     /* Parse out names of players */
7143     while (*game == ' ') game++;
7144     p = buf;
7145     while (*game != ' ') *p++ = *game++;
7146     *p = NULLCHAR;
7147     gameInfo.white = StrSave(buf);
7148     while (*game == ' ') game++;
7149     p = buf;
7150     while (*game != ' ' && *game != '\n') *p++ = *game++;
7151     *p = NULLCHAR;
7152     gameInfo.black = StrSave(buf);
7153
7154     /* Parse moves */
7155     boardIndex = blackPlaysFirst ? 1 : 0;
7156     yynewstr(game);
7157     for (;;) {
7158         yyboardindex = boardIndex;
7159         moveType = (ChessMove) yylex();
7160         switch (moveType) {
7161           case IllegalMove:             /* maybe suicide chess, etc. */
7162   if (appData.debugMode) {
7163     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7164     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7165     setbuf(debugFP, NULL);
7166   }
7167           case WhitePromotionChancellor:
7168           case BlackPromotionChancellor:
7169           case WhitePromotionArchbishop:
7170           case BlackPromotionArchbishop:
7171           case WhitePromotionQueen:
7172           case BlackPromotionQueen:
7173           case WhitePromotionRook:
7174           case BlackPromotionRook:
7175           case WhitePromotionBishop:
7176           case BlackPromotionBishop:
7177           case WhitePromotionKnight:
7178           case BlackPromotionKnight:
7179           case WhitePromotionKing:
7180           case BlackPromotionKing:
7181           case NormalMove:
7182           case WhiteCapturesEnPassant:
7183           case BlackCapturesEnPassant:
7184           case WhiteKingSideCastle:
7185           case WhiteQueenSideCastle:
7186           case BlackKingSideCastle:
7187           case BlackQueenSideCastle:
7188           case WhiteKingSideCastleWild:
7189           case WhiteQueenSideCastleWild:
7190           case BlackKingSideCastleWild:
7191           case BlackQueenSideCastleWild:
7192           /* PUSH Fabien */
7193           case WhiteHSideCastleFR:
7194           case WhiteASideCastleFR:
7195           case BlackHSideCastleFR:
7196           case BlackASideCastleFR:
7197           /* POP Fabien */
7198             fromX = currentMoveString[0] - AAA;
7199             fromY = currentMoveString[1] - ONE;
7200             toX = currentMoveString[2] - AAA;
7201             toY = currentMoveString[3] - ONE;
7202             promoChar = currentMoveString[4];
7203             break;
7204           case WhiteDrop:
7205           case BlackDrop:
7206             fromX = moveType == WhiteDrop ?
7207               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7208             (int) CharToPiece(ToLower(currentMoveString[0]));
7209             fromY = DROP_RANK;
7210             toX = currentMoveString[2] - AAA;
7211             toY = currentMoveString[3] - ONE;
7212             promoChar = NULLCHAR;
7213             break;
7214           case AmbiguousMove:
7215             /* bug? */
7216             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7217   if (appData.debugMode) {
7218     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7219     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7220     setbuf(debugFP, NULL);
7221   }
7222             DisplayError(buf, 0);
7223             return;
7224           case ImpossibleMove:
7225             /* bug? */
7226             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7227   if (appData.debugMode) {
7228     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7229     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7230     setbuf(debugFP, NULL);
7231   }
7232             DisplayError(buf, 0);
7233             return;
7234           case (ChessMove) 0:   /* end of file */
7235             if (boardIndex < backwardMostMove) {
7236                 /* Oops, gap.  How did that happen? */
7237                 DisplayError(_("Gap in move list"), 0);
7238                 return;
7239             }
7240             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7241             if (boardIndex > forwardMostMove) {
7242                 forwardMostMove = boardIndex;
7243             }
7244             return;
7245           case ElapsedTime:
7246             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7247                 strcat(parseList[boardIndex-1], " ");
7248                 strcat(parseList[boardIndex-1], yy_text);
7249             }
7250             continue;
7251           case Comment:
7252           case PGNTag:
7253           case NAG:
7254           default:
7255             /* ignore */
7256             continue;
7257           case WhiteWins:
7258           case BlackWins:
7259           case GameIsDrawn:
7260           case GameUnfinished:
7261             if (gameMode == IcsExamining) {
7262                 if (boardIndex < backwardMostMove) {
7263                     /* Oops, gap.  How did that happen? */
7264                     return;
7265                 }
7266                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7267                 return;
7268             }
7269             gameInfo.result = moveType;
7270             p = strchr(yy_text, '{');
7271             if (p == NULL) p = strchr(yy_text, '(');
7272             if (p == NULL) {
7273                 p = yy_text;
7274                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7275             } else {
7276                 q = strchr(p, *p == '{' ? '}' : ')');
7277                 if (q != NULL) *q = NULLCHAR;
7278                 p++;
7279             }
7280             gameInfo.resultDetails = StrSave(p);
7281             continue;
7282         }
7283         if (boardIndex >= forwardMostMove &&
7284             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7285             backwardMostMove = blackPlaysFirst ? 1 : 0;
7286             return;
7287         }
7288         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7289                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7290                                  parseList[boardIndex]);
7291         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7292         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7293         /* currentMoveString is set as a side-effect of yylex */
7294         strcpy(moveList[boardIndex], currentMoveString);
7295         strcat(moveList[boardIndex], "\n");
7296         boardIndex++;
7297         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7298                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7299         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7300                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7301           case MT_NONE:
7302           case MT_STALEMATE:
7303           default:
7304             break;
7305           case MT_CHECK:
7306             if(gameInfo.variant != VariantShogi)
7307                 strcat(parseList[boardIndex - 1], "+");
7308             break;
7309           case MT_CHECKMATE:
7310           case MT_STAINMATE:
7311             strcat(parseList[boardIndex - 1], "#");
7312             break;
7313         }
7314     }
7315 }
7316
7317
7318 /* Apply a move to the given board  */
7319 void
7320 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7321      int fromX, fromY, toX, toY;
7322      int promoChar;
7323      Board board;
7324      char *castling;
7325      char *ep;
7326 {
7327   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7328
7329     /* [HGM] compute & store e.p. status and castling rights for new position */
7330     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7331     { int i;
7332
7333       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7334       oldEP = *ep;
7335       *ep = EP_NONE;
7336
7337       if( board[toY][toX] != EmptySquare ) 
7338            *ep = EP_CAPTURE;  
7339
7340       if( board[fromY][fromX] == WhitePawn ) {
7341            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7342                *ep = EP_PAWN_MOVE;
7343            if( toY-fromY==2) {
7344                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7345                         gameInfo.variant != VariantBerolina || toX < fromX)
7346                       *ep = toX | berolina;
7347                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7348                         gameInfo.variant != VariantBerolina || toX > fromX) 
7349                       *ep = toX;
7350            }
7351       } else 
7352       if( board[fromY][fromX] == BlackPawn ) {
7353            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7354                *ep = EP_PAWN_MOVE; 
7355            if( toY-fromY== -2) {
7356                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7357                         gameInfo.variant != VariantBerolina || toX < fromX)
7358                       *ep = toX | berolina;
7359                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7360                         gameInfo.variant != VariantBerolina || toX > fromX) 
7361                       *ep = toX;
7362            }
7363        }
7364
7365        for(i=0; i<nrCastlingRights; i++) {
7366            if(castling[i] == fromX && castlingRank[i] == fromY ||
7367               castling[i] == toX   && castlingRank[i] == toY   
7368              ) castling[i] = -1; // revoke for moved or captured piece
7369        }
7370
7371     }
7372
7373   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7374   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7375        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7376          
7377   if (fromX == toX && fromY == toY) return;
7378
7379   if (fromY == DROP_RANK) {
7380         /* must be first */
7381         piece = board[toY][toX] = (ChessSquare) fromX;
7382   } else {
7383      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7384      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7385      if(gameInfo.variant == VariantKnightmate)
7386          king += (int) WhiteUnicorn - (int) WhiteKing;
7387
7388     /* Code added by Tord: */
7389     /* FRC castling assumed when king captures friendly rook. */
7390     if (board[fromY][fromX] == WhiteKing &&
7391              board[toY][toX] == WhiteRook) {
7392       board[fromY][fromX] = EmptySquare;
7393       board[toY][toX] = EmptySquare;
7394       if(toX > fromX) {
7395         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7396       } else {
7397         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7398       }
7399     } else if (board[fromY][fromX] == BlackKing &&
7400                board[toY][toX] == BlackRook) {
7401       board[fromY][fromX] = EmptySquare;
7402       board[toY][toX] = EmptySquare;
7403       if(toX > fromX) {
7404         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7405       } else {
7406         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7407       }
7408     /* End of code added by Tord */
7409
7410     } else if (board[fromY][fromX] == king
7411         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7412         && toY == fromY && toX > fromX+1) {
7413         board[fromY][fromX] = EmptySquare;
7414         board[toY][toX] = king;
7415         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7416         board[fromY][BOARD_RGHT-1] = EmptySquare;
7417     } else if (board[fromY][fromX] == king
7418         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7419                && toY == fromY && toX < fromX-1) {
7420         board[fromY][fromX] = EmptySquare;
7421         board[toY][toX] = king;
7422         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7423         board[fromY][BOARD_LEFT] = EmptySquare;
7424     } else if (board[fromY][fromX] == WhitePawn
7425                && toY == BOARD_HEIGHT-1
7426                && gameInfo.variant != VariantXiangqi
7427                ) {
7428         /* white pawn promotion */
7429         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7430         if (board[toY][toX] == EmptySquare) {
7431             board[toY][toX] = WhiteQueen;
7432         }
7433         if(gameInfo.variant==VariantBughouse ||
7434            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7435             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7436         board[fromY][fromX] = EmptySquare;
7437     } else if ((fromY == BOARD_HEIGHT-4)
7438                && (toX != fromX)
7439                && gameInfo.variant != VariantXiangqi
7440                && gameInfo.variant != VariantBerolina
7441                && (board[fromY][fromX] == WhitePawn)
7442                && (board[toY][toX] == EmptySquare)) {
7443         board[fromY][fromX] = EmptySquare;
7444         board[toY][toX] = WhitePawn;
7445         captured = board[toY - 1][toX];
7446         board[toY - 1][toX] = EmptySquare;
7447     } else if ((fromY == BOARD_HEIGHT-4)
7448                && (toX == fromX)
7449                && gameInfo.variant == VariantBerolina
7450                && (board[fromY][fromX] == WhitePawn)
7451                && (board[toY][toX] == EmptySquare)) {
7452         board[fromY][fromX] = EmptySquare;
7453         board[toY][toX] = WhitePawn;
7454         if(oldEP & EP_BEROLIN_A) {
7455                 captured = board[fromY][fromX-1];
7456                 board[fromY][fromX-1] = EmptySquare;
7457         }else{  captured = board[fromY][fromX+1];
7458                 board[fromY][fromX+1] = EmptySquare;
7459         }
7460     } else if (board[fromY][fromX] == king
7461         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7462                && toY == fromY && toX > fromX+1) {
7463         board[fromY][fromX] = EmptySquare;
7464         board[toY][toX] = king;
7465         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7466         board[fromY][BOARD_RGHT-1] = EmptySquare;
7467     } else if (board[fromY][fromX] == king
7468         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7469                && toY == fromY && toX < fromX-1) {
7470         board[fromY][fromX] = EmptySquare;
7471         board[toY][toX] = king;
7472         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7473         board[fromY][BOARD_LEFT] = EmptySquare;
7474     } else if (fromY == 7 && fromX == 3
7475                && board[fromY][fromX] == BlackKing
7476                && toY == 7 && toX == 5) {
7477         board[fromY][fromX] = EmptySquare;
7478         board[toY][toX] = BlackKing;
7479         board[fromY][7] = EmptySquare;
7480         board[toY][4] = BlackRook;
7481     } else if (fromY == 7 && fromX == 3
7482                && board[fromY][fromX] == BlackKing
7483                && toY == 7 && toX == 1) {
7484         board[fromY][fromX] = EmptySquare;
7485         board[toY][toX] = BlackKing;
7486         board[fromY][0] = EmptySquare;
7487         board[toY][2] = BlackRook;
7488     } else if (board[fromY][fromX] == BlackPawn
7489                && toY == 0
7490                && gameInfo.variant != VariantXiangqi
7491                ) {
7492         /* black pawn promotion */
7493         board[0][toX] = CharToPiece(ToLower(promoChar));
7494         if (board[0][toX] == EmptySquare) {
7495             board[0][toX] = BlackQueen;
7496         }
7497         if(gameInfo.variant==VariantBughouse ||
7498            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7499             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7500         board[fromY][fromX] = EmptySquare;
7501     } else if ((fromY == 3)
7502                && (toX != fromX)
7503                && gameInfo.variant != VariantXiangqi
7504                && gameInfo.variant != VariantBerolina
7505                && (board[fromY][fromX] == BlackPawn)
7506                && (board[toY][toX] == EmptySquare)) {
7507         board[fromY][fromX] = EmptySquare;
7508         board[toY][toX] = BlackPawn;
7509         captured = board[toY + 1][toX];
7510         board[toY + 1][toX] = EmptySquare;
7511     } else if ((fromY == 3)
7512                && (toX == fromX)
7513                && gameInfo.variant == VariantBerolina
7514                && (board[fromY][fromX] == BlackPawn)
7515                && (board[toY][toX] == EmptySquare)) {
7516         board[fromY][fromX] = EmptySquare;
7517         board[toY][toX] = BlackPawn;
7518         if(oldEP & EP_BEROLIN_A) {
7519                 captured = board[fromY][fromX-1];
7520                 board[fromY][fromX-1] = EmptySquare;
7521         }else{  captured = board[fromY][fromX+1];
7522                 board[fromY][fromX+1] = EmptySquare;
7523         }
7524     } else {
7525         board[toY][toX] = board[fromY][fromX];
7526         board[fromY][fromX] = EmptySquare;
7527     }
7528
7529     /* [HGM] now we promote for Shogi, if needed */
7530     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7531         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7532   }
7533
7534     if (gameInfo.holdingsWidth != 0) {
7535
7536       /* !!A lot more code needs to be written to support holdings  */
7537       /* [HGM] OK, so I have written it. Holdings are stored in the */
7538       /* penultimate board files, so they are automaticlly stored   */
7539       /* in the game history.                                       */
7540       if (fromY == DROP_RANK) {
7541         /* Delete from holdings, by decreasing count */
7542         /* and erasing image if necessary            */
7543         p = (int) fromX;
7544         if(p < (int) BlackPawn) { /* white drop */
7545              p -= (int)WhitePawn;
7546                  p = PieceToNumber((ChessSquare)p);
7547              if(p >= gameInfo.holdingsSize) p = 0;
7548              if(--board[p][BOARD_WIDTH-2] <= 0)
7549                   board[p][BOARD_WIDTH-1] = EmptySquare;
7550              if((int)board[p][BOARD_WIDTH-2] < 0)
7551                         board[p][BOARD_WIDTH-2] = 0;
7552         } else {                  /* black drop */
7553              p -= (int)BlackPawn;
7554                  p = PieceToNumber((ChessSquare)p);
7555              if(p >= gameInfo.holdingsSize) p = 0;
7556              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7557                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7558              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7559                         board[BOARD_HEIGHT-1-p][1] = 0;
7560         }
7561       }
7562       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7563           && gameInfo.variant != VariantBughouse        ) {
7564         /* [HGM] holdings: Add to holdings, if holdings exist */
7565         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7566                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7567                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7568         }
7569         p = (int) captured;
7570         if (p >= (int) BlackPawn) {
7571           p -= (int)BlackPawn;
7572           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7573                   /* in Shogi restore piece to its original  first */
7574                   captured = (ChessSquare) (DEMOTED captured);
7575                   p = DEMOTED p;
7576           }
7577           p = PieceToNumber((ChessSquare)p);
7578           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7579           board[p][BOARD_WIDTH-2]++;
7580           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7581         } else {
7582           p -= (int)WhitePawn;
7583           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7584                   captured = (ChessSquare) (DEMOTED captured);
7585                   p = DEMOTED p;
7586           }
7587           p = PieceToNumber((ChessSquare)p);
7588           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7589           board[BOARD_HEIGHT-1-p][1]++;
7590           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7591         }
7592       }
7593     } else if (gameInfo.variant == VariantAtomic) {
7594       if (captured != EmptySquare) {
7595         int y, x;
7596         for (y = toY-1; y <= toY+1; y++) {
7597           for (x = toX-1; x <= toX+1; x++) {
7598             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7599                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7600               board[y][x] = EmptySquare;
7601             }
7602           }
7603         }
7604         board[toY][toX] = EmptySquare;
7605       }
7606     }
7607     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7608         /* [HGM] Shogi promotions */
7609         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7610     }
7611
7612     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7613                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7614         // [HGM] superchess: take promotion piece out of holdings
7615         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7616         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7617             if(!--board[k][BOARD_WIDTH-2])
7618                 board[k][BOARD_WIDTH-1] = EmptySquare;
7619         } else {
7620             if(!--board[BOARD_HEIGHT-1-k][1])
7621                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7622         }
7623     }
7624
7625 }
7626
7627 /* Updates forwardMostMove */
7628 void
7629 MakeMove(fromX, fromY, toX, toY, promoChar)
7630      int fromX, fromY, toX, toY;
7631      int promoChar;
7632 {
7633 //    forwardMostMove++; // [HGM] bare: moved downstream
7634
7635     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7636         int timeLeft; static int lastLoadFlag=0; int king, piece;
7637         piece = boards[forwardMostMove][fromY][fromX];
7638         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7639         if(gameInfo.variant == VariantKnightmate)
7640             king += (int) WhiteUnicorn - (int) WhiteKing;
7641         if(forwardMostMove == 0) {
7642             if(blackPlaysFirst) 
7643                 fprintf(serverMoves, "%s;", second.tidy);
7644             fprintf(serverMoves, "%s;", first.tidy);
7645             if(!blackPlaysFirst) 
7646                 fprintf(serverMoves, "%s;", second.tidy);
7647         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7648         lastLoadFlag = loadFlag;
7649         // print base move
7650         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7651         // print castling suffix
7652         if( toY == fromY && piece == king ) {
7653             if(toX-fromX > 1)
7654                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7655             if(fromX-toX >1)
7656                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7657         }
7658         // e.p. suffix
7659         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7660              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7661              boards[forwardMostMove][toY][toX] == EmptySquare
7662              && fromX != toX )
7663                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7664         // promotion suffix
7665         if(promoChar != NULLCHAR)
7666                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7667         if(!loadFlag) {
7668             fprintf(serverMoves, "/%d/%d",
7669                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7670             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7671             else                      timeLeft = blackTimeRemaining/1000;
7672             fprintf(serverMoves, "/%d", timeLeft);
7673         }
7674         fflush(serverMoves);
7675     }
7676
7677     if (forwardMostMove+1 >= MAX_MOVES) {
7678       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7679                         0, 1);
7680       return;
7681     }
7682     if (commentList[forwardMostMove+1] != NULL) {
7683         free(commentList[forwardMostMove+1]);
7684         commentList[forwardMostMove+1] = NULL;
7685     }
7686     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7687     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7688     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7689                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7690     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7691     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7692     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7693     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7694     gameInfo.result = GameUnfinished;
7695     if (gameInfo.resultDetails != NULL) {
7696         free(gameInfo.resultDetails);
7697         gameInfo.resultDetails = NULL;
7698     }
7699     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7700                               moveList[forwardMostMove - 1]);
7701     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7702                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7703                              fromY, fromX, toY, toX, promoChar,
7704                              parseList[forwardMostMove - 1]);
7705     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7706                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7707                             castlingRights[forwardMostMove]) ) {
7708       case MT_NONE:
7709       case MT_STALEMATE:
7710       default:
7711         break;
7712       case MT_CHECK:
7713         if(gameInfo.variant != VariantShogi)
7714             strcat(parseList[forwardMostMove - 1], "+");
7715         break;
7716       case MT_CHECKMATE:
7717       case MT_STAINMATE:
7718         strcat(parseList[forwardMostMove - 1], "#");
7719         break;
7720     }
7721     if (appData.debugMode) {
7722         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7723     }
7724
7725 }
7726
7727 /* Updates currentMove if not pausing */
7728 void
7729 ShowMove(fromX, fromY, toX, toY)
7730 {
7731     int instant = (gameMode == PlayFromGameFile) ?
7732         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7733     if(appData.noGUI) return;
7734     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7735         if (!instant) {
7736             if (forwardMostMove == currentMove + 1) {
7737                 AnimateMove(boards[forwardMostMove - 1],
7738                             fromX, fromY, toX, toY);
7739             }
7740             if (appData.highlightLastMove) {
7741                 SetHighlights(fromX, fromY, toX, toY);
7742             }
7743         }
7744         currentMove = forwardMostMove;
7745     }
7746
7747     if (instant) return;
7748
7749     DisplayMove(currentMove - 1);
7750     DrawPosition(FALSE, boards[currentMove]);
7751     DisplayBothClocks();
7752     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7753 }
7754
7755 void SendEgtPath(ChessProgramState *cps)
7756 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7757         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7758
7759         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7760
7761         while(*p) {
7762             char c, *q = name+1, *r, *s;
7763
7764             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7765             while(*p && *p != ',') *q++ = *p++;
7766             *q++ = ':'; *q = 0;
7767             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7768                 strcmp(name, ",nalimov:") == 0 ) {
7769                 // take nalimov path from the menu-changeable option first, if it is defined
7770                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7771                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7772             } else
7773             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7774                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7775                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7776                 s = r = StrStr(s, ":") + 1; // beginning of path info
7777                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7778                 c = *r; *r = 0;             // temporarily null-terminate path info
7779                     *--q = 0;               // strip of trailig ':' from name
7780                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7781                 *r = c;
7782                 SendToProgram(buf,cps);     // send egtbpath command for this format
7783             }
7784             if(*p == ',') p++; // read away comma to position for next format name
7785         }
7786 }
7787
7788 void
7789 InitChessProgram(cps, setup)
7790      ChessProgramState *cps;
7791      int setup; /* [HGM] needed to setup FRC opening position */
7792 {
7793     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7794     if (appData.noChessProgram) return;
7795     hintRequested = FALSE;
7796     bookRequested = FALSE;
7797
7798     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7799     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7800     if(cps->memSize) { /* [HGM] memory */
7801         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7802         SendToProgram(buf, cps);
7803     }
7804     SendEgtPath(cps); /* [HGM] EGT */
7805     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7806         sprintf(buf, "cores %d\n", appData.smpCores);
7807         SendToProgram(buf, cps);
7808     }
7809
7810     SendToProgram(cps->initString, cps);
7811     if (gameInfo.variant != VariantNormal &&
7812         gameInfo.variant != VariantLoadable
7813         /* [HGM] also send variant if board size non-standard */
7814         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7815                                             ) {
7816       char *v = VariantName(gameInfo.variant);
7817       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7818         /* [HGM] in protocol 1 we have to assume all variants valid */
7819         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7820         DisplayFatalError(buf, 0, 1);
7821         return;
7822       }
7823
7824       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7825       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7826       if( gameInfo.variant == VariantXiangqi )
7827            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7828       if( gameInfo.variant == VariantShogi )
7829            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7830       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7831            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7832       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7833                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7834            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7835       if( gameInfo.variant == VariantCourier )
7836            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7837       if( gameInfo.variant == VariantSuper )
7838            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7839       if( gameInfo.variant == VariantGreat )
7840            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7841
7842       if(overruled) {
7843            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7844                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7845            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7846            if(StrStr(cps->variants, b) == NULL) { 
7847                // specific sized variant not known, check if general sizing allowed
7848                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7849                    if(StrStr(cps->variants, "boardsize") == NULL) {
7850                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7851                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7852                        DisplayFatalError(buf, 0, 1);
7853                        return;
7854                    }
7855                    /* [HGM] here we really should compare with the maximum supported board size */
7856                }
7857            }
7858       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7859       sprintf(buf, "variant %s\n", b);
7860       SendToProgram(buf, cps);
7861     }
7862     currentlyInitializedVariant = gameInfo.variant;
7863
7864     /* [HGM] send opening position in FRC to first engine */
7865     if(setup) {
7866           SendToProgram("force\n", cps);
7867           SendBoard(cps, 0);
7868           /* engine is now in force mode! Set flag to wake it up after first move. */
7869           setboardSpoiledMachineBlack = 1;
7870     }
7871
7872     if (cps->sendICS) {
7873       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7874       SendToProgram(buf, cps);
7875     }
7876     cps->maybeThinking = FALSE;
7877     cps->offeredDraw = 0;
7878     if (!appData.icsActive) {
7879         SendTimeControl(cps, movesPerSession, timeControl,
7880                         timeIncrement, appData.searchDepth,
7881                         searchTime);
7882     }
7883     if (appData.showThinking 
7884         // [HGM] thinking: four options require thinking output to be sent
7885         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7886                                 ) {
7887         SendToProgram("post\n", cps);
7888     }
7889     SendToProgram("hard\n", cps);
7890     if (!appData.ponderNextMove) {
7891         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7892            it without being sure what state we are in first.  "hard"
7893            is not a toggle, so that one is OK.
7894          */
7895         SendToProgram("easy\n", cps);
7896     }
7897     if (cps->usePing) {
7898       sprintf(buf, "ping %d\n", ++cps->lastPing);
7899       SendToProgram(buf, cps);
7900     }
7901     cps->initDone = TRUE;
7902 }   
7903
7904
7905 void
7906 StartChessProgram(cps)
7907      ChessProgramState *cps;
7908 {
7909     char buf[MSG_SIZ];
7910     int err;
7911
7912     if (appData.noChessProgram) return;
7913     cps->initDone = FALSE;
7914
7915     if (strcmp(cps->host, "localhost") == 0) {
7916         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7917     } else if (*appData.remoteShell == NULLCHAR) {
7918         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7919     } else {
7920         if (*appData.remoteUser == NULLCHAR) {
7921           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7922                     cps->program);
7923         } else {
7924           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7925                     cps->host, appData.remoteUser, cps->program);
7926         }
7927         err = StartChildProcess(buf, "", &cps->pr);
7928     }
7929     
7930     if (err != 0) {
7931         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7932         DisplayFatalError(buf, err, 1);
7933         cps->pr = NoProc;
7934         cps->isr = NULL;
7935         return;
7936     }
7937     
7938     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7939     if (cps->protocolVersion > 1) {
7940       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7941       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7942       cps->comboCnt = 0;  //                and values of combo boxes
7943       SendToProgram(buf, cps);
7944     } else {
7945       SendToProgram("xboard\n", cps);
7946     }
7947 }
7948
7949
7950 void
7951 TwoMachinesEventIfReady P((void))
7952 {
7953   if (first.lastPing != first.lastPong) {
7954     DisplayMessage("", _("Waiting for first chess program"));
7955     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7956     return;
7957   }
7958   if (second.lastPing != second.lastPong) {
7959     DisplayMessage("", _("Waiting for second chess program"));
7960     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7961     return;
7962   }
7963   ThawUI();
7964   TwoMachinesEvent();
7965 }
7966
7967 void
7968 NextMatchGame P((void))
7969 {
7970     int index; /* [HGM] autoinc: step lod index during match */
7971     Reset(FALSE, TRUE);
7972     if (*appData.loadGameFile != NULLCHAR) {
7973         index = appData.loadGameIndex;
7974         if(index < 0) { // [HGM] autoinc
7975             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7976             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7977         } 
7978         LoadGameFromFile(appData.loadGameFile,
7979                          index,
7980                          appData.loadGameFile, FALSE);
7981     } else if (*appData.loadPositionFile != NULLCHAR) {
7982         index = appData.loadPositionIndex;
7983         if(index < 0) { // [HGM] autoinc
7984             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7985             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7986         } 
7987         LoadPositionFromFile(appData.loadPositionFile,
7988                              index,
7989                              appData.loadPositionFile);
7990     }
7991     TwoMachinesEventIfReady();
7992 }
7993
7994 void UserAdjudicationEvent( int result )
7995 {
7996     ChessMove gameResult = GameIsDrawn;
7997
7998     if( result > 0 ) {
7999         gameResult = WhiteWins;
8000     }
8001     else if( result < 0 ) {
8002         gameResult = BlackWins;
8003     }
8004
8005     if( gameMode == TwoMachinesPlay ) {
8006         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8007     }
8008 }
8009
8010
8011 // [HGM] save: calculate checksum of game to make games easily identifiable
8012 int StringCheckSum(char *s)
8013 {
8014         int i = 0;
8015         if(s==NULL) return 0;
8016         while(*s) i = i*259 + *s++;
8017         return i;
8018 }
8019
8020 int GameCheckSum()
8021 {
8022         int i, sum=0;
8023         for(i=backwardMostMove; i<forwardMostMove; i++) {
8024                 sum += pvInfoList[i].depth;
8025                 sum += StringCheckSum(parseList[i]);
8026                 sum += StringCheckSum(commentList[i]);
8027                 sum *= 261;
8028         }
8029         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8030         return sum + StringCheckSum(commentList[i]);
8031 } // end of save patch
8032
8033 void
8034 GameEnds(result, resultDetails, whosays)
8035      ChessMove result;
8036      char *resultDetails;
8037      int whosays;
8038 {
8039     GameMode nextGameMode;
8040     int isIcsGame;
8041     char buf[MSG_SIZ];
8042
8043     if(endingGame) return; /* [HGM] crash: forbid recursion */
8044     endingGame = 1;
8045
8046     if (appData.debugMode) {
8047       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8048               result, resultDetails ? resultDetails : "(null)", whosays);
8049     }
8050
8051     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8052         /* If we are playing on ICS, the server decides when the
8053            game is over, but the engine can offer to draw, claim 
8054            a draw, or resign. 
8055          */
8056 #if ZIPPY
8057         if (appData.zippyPlay && first.initDone) {
8058             if (result == GameIsDrawn) {
8059                 /* In case draw still needs to be claimed */
8060                 SendToICS(ics_prefix);
8061                 SendToICS("draw\n");
8062             } else if (StrCaseStr(resultDetails, "resign")) {
8063                 SendToICS(ics_prefix);
8064                 SendToICS("resign\n");
8065             }
8066         }
8067 #endif
8068         endingGame = 0; /* [HGM] crash */
8069         return;
8070     }
8071
8072     /* If we're loading the game from a file, stop */
8073     if (whosays == GE_FILE) {
8074       (void) StopLoadGameTimer();
8075       gameFileFP = NULL;
8076     }
8077
8078     /* Cancel draw offers */
8079     first.offeredDraw = second.offeredDraw = 0;
8080
8081     /* If this is an ICS game, only ICS can really say it's done;
8082        if not, anyone can. */
8083     isIcsGame = (gameMode == IcsPlayingWhite || 
8084                  gameMode == IcsPlayingBlack || 
8085                  gameMode == IcsObserving    || 
8086                  gameMode == IcsExamining);
8087
8088     if (!isIcsGame || whosays == GE_ICS) {
8089         /* OK -- not an ICS game, or ICS said it was done */
8090         StopClocks();
8091         if (!isIcsGame && !appData.noChessProgram) 
8092           SetUserThinkingEnables();
8093     
8094         /* [HGM] if a machine claims the game end we verify this claim */
8095         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8096             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8097                 char claimer;
8098                 ChessMove trueResult = (ChessMove) -1;
8099
8100                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8101                                             first.twoMachinesColor[0] :
8102                                             second.twoMachinesColor[0] ;
8103
8104                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8105                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8106                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8107                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8108                 } else
8109                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8110                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8111                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8112                 } else
8113                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8114                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8115                 }
8116
8117                 // now verify win claims, but not in drop games, as we don't understand those yet
8118                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8119                                                  || gameInfo.variant == VariantGreat) &&
8120                     (result == WhiteWins && claimer == 'w' ||
8121                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8122                       if (appData.debugMode) {
8123                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8124                                 result, epStatus[forwardMostMove], forwardMostMove);
8125                       }
8126                       if(result != trueResult) {
8127                               sprintf(buf, "False win claim: '%s'", resultDetails);
8128                               result = claimer == 'w' ? BlackWins : WhiteWins;
8129                               resultDetails = buf;
8130                       }
8131                 } else
8132                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8133                     && (forwardMostMove <= backwardMostMove ||
8134                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8135                         (claimer=='b')==(forwardMostMove&1))
8136                                                                                   ) {
8137                       /* [HGM] verify: draws that were not flagged are false claims */
8138                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8139                       result = claimer == 'w' ? BlackWins : WhiteWins;
8140                       resultDetails = buf;
8141                 }
8142                 /* (Claiming a loss is accepted no questions asked!) */
8143             }
8144             /* [HGM] bare: don't allow bare King to win */
8145             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8146                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8147                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8148                && result != GameIsDrawn)
8149             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8150                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8151                         int p = (int)boards[forwardMostMove][i][j] - color;
8152                         if(p >= 0 && p <= (int)WhiteKing) k++;
8153                 }
8154                 if (appData.debugMode) {
8155                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8156                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8157                 }
8158                 if(k <= 1) {
8159                         result = GameIsDrawn;
8160                         sprintf(buf, "%s but bare king", resultDetails);
8161                         resultDetails = buf;
8162                 }
8163             }
8164         }
8165
8166
8167         if(serverMoves != NULL && !loadFlag) { char c = '=';
8168             if(result==WhiteWins) c = '+';
8169             if(result==BlackWins) c = '-';
8170             if(resultDetails != NULL)
8171                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8172         }
8173         if (resultDetails != NULL) {
8174             gameInfo.result = result;
8175             gameInfo.resultDetails = StrSave(resultDetails);
8176
8177             /* display last move only if game was not loaded from file */
8178             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8179                 DisplayMove(currentMove - 1);
8180     
8181             if (forwardMostMove != 0) {
8182                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8183                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8184                                                                 ) {
8185                     if (*appData.saveGameFile != NULLCHAR) {
8186                         SaveGameToFile(appData.saveGameFile, TRUE);
8187                     } else if (appData.autoSaveGames) {
8188                         AutoSaveGame();
8189                     }
8190                     if (*appData.savePositionFile != NULLCHAR) {
8191                         SavePositionToFile(appData.savePositionFile);
8192                     }
8193                 }
8194             }
8195
8196             /* Tell program how game ended in case it is learning */
8197             /* [HGM] Moved this to after saving the PGN, just in case */
8198             /* engine died and we got here through time loss. In that */
8199             /* case we will get a fatal error writing the pipe, which */
8200             /* would otherwise lose us the PGN.                       */
8201             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8202             /* output during GameEnds should never be fatal anymore   */
8203             if (gameMode == MachinePlaysWhite ||
8204                 gameMode == MachinePlaysBlack ||
8205                 gameMode == TwoMachinesPlay ||
8206                 gameMode == IcsPlayingWhite ||
8207                 gameMode == IcsPlayingBlack ||
8208                 gameMode == BeginningOfGame) {
8209                 char buf[MSG_SIZ];
8210                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8211                         resultDetails);
8212                 if (first.pr != NoProc) {
8213                     SendToProgram(buf, &first);
8214                 }
8215                 if (second.pr != NoProc &&
8216                     gameMode == TwoMachinesPlay) {
8217                     SendToProgram(buf, &second);
8218                 }
8219             }
8220         }
8221
8222         if (appData.icsActive) {
8223             if (appData.quietPlay &&
8224                 (gameMode == IcsPlayingWhite ||
8225                  gameMode == IcsPlayingBlack)) {
8226                 SendToICS(ics_prefix);
8227                 SendToICS("set shout 1\n");
8228             }
8229             nextGameMode = IcsIdle;
8230             ics_user_moved = FALSE;
8231             /* clean up premove.  It's ugly when the game has ended and the
8232              * premove highlights are still on the board.
8233              */
8234             if (gotPremove) {
8235               gotPremove = FALSE;
8236               ClearPremoveHighlights();
8237               DrawPosition(FALSE, boards[currentMove]);
8238             }
8239             if (whosays == GE_ICS) {
8240                 switch (result) {
8241                 case WhiteWins:
8242                     if (gameMode == IcsPlayingWhite)
8243                         PlayIcsWinSound();
8244                     else if(gameMode == IcsPlayingBlack)
8245                         PlayIcsLossSound();
8246                     break;
8247                 case BlackWins:
8248                     if (gameMode == IcsPlayingBlack)
8249                         PlayIcsWinSound();
8250                     else if(gameMode == IcsPlayingWhite)
8251                         PlayIcsLossSound();
8252                     break;
8253                 case GameIsDrawn:
8254                     PlayIcsDrawSound();
8255                     break;
8256                 default:
8257                     PlayIcsUnfinishedSound();
8258                 }
8259             }
8260         } else if (gameMode == EditGame ||
8261                    gameMode == PlayFromGameFile || 
8262                    gameMode == AnalyzeMode || 
8263                    gameMode == AnalyzeFile) {
8264             nextGameMode = gameMode;
8265         } else {
8266             nextGameMode = EndOfGame;
8267         }
8268         pausing = FALSE;
8269         ModeHighlight();
8270     } else {
8271         nextGameMode = gameMode;
8272     }
8273
8274     if (appData.noChessProgram) {
8275         gameMode = nextGameMode;
8276         ModeHighlight();
8277         endingGame = 0; /* [HGM] crash */
8278         return;
8279     }
8280
8281     if (first.reuse) {
8282         /* Put first chess program into idle state */
8283         if (first.pr != NoProc &&
8284             (gameMode == MachinePlaysWhite ||
8285              gameMode == MachinePlaysBlack ||
8286              gameMode == TwoMachinesPlay ||
8287              gameMode == IcsPlayingWhite ||
8288              gameMode == IcsPlayingBlack ||
8289              gameMode == BeginningOfGame)) {
8290             SendToProgram("force\n", &first);
8291             if (first.usePing) {
8292               char buf[MSG_SIZ];
8293               sprintf(buf, "ping %d\n", ++first.lastPing);
8294               SendToProgram(buf, &first);
8295             }
8296         }
8297     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8298         /* Kill off first chess program */
8299         if (first.isr != NULL)
8300           RemoveInputSource(first.isr);
8301         first.isr = NULL;
8302     
8303         if (first.pr != NoProc) {
8304             ExitAnalyzeMode();
8305             DoSleep( appData.delayBeforeQuit );
8306             SendToProgram("quit\n", &first);
8307             DoSleep( appData.delayAfterQuit );
8308             DestroyChildProcess(first.pr, first.useSigterm);
8309         }
8310         first.pr = NoProc;
8311     }
8312     if (second.reuse) {
8313         /* Put second chess program into idle state */
8314         if (second.pr != NoProc &&
8315             gameMode == TwoMachinesPlay) {
8316             SendToProgram("force\n", &second);
8317             if (second.usePing) {
8318               char buf[MSG_SIZ];
8319               sprintf(buf, "ping %d\n", ++second.lastPing);
8320               SendToProgram(buf, &second);
8321             }
8322         }
8323     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8324         /* Kill off second chess program */
8325         if (second.isr != NULL)
8326           RemoveInputSource(second.isr);
8327         second.isr = NULL;
8328     
8329         if (second.pr != NoProc) {
8330             DoSleep( appData.delayBeforeQuit );
8331             SendToProgram("quit\n", &second);
8332             DoSleep( appData.delayAfterQuit );
8333             DestroyChildProcess(second.pr, second.useSigterm);
8334         }
8335         second.pr = NoProc;
8336     }
8337
8338     if (matchMode && gameMode == TwoMachinesPlay) {
8339         switch (result) {
8340         case WhiteWins:
8341           if (first.twoMachinesColor[0] == 'w') {
8342             first.matchWins++;
8343           } else {
8344             second.matchWins++;
8345           }
8346           break;
8347         case BlackWins:
8348           if (first.twoMachinesColor[0] == 'b') {
8349             first.matchWins++;
8350           } else {
8351             second.matchWins++;
8352           }
8353           break;
8354         default:
8355           break;
8356         }
8357         if (matchGame < appData.matchGames) {
8358             char *tmp;
8359             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8360                 tmp = first.twoMachinesColor;
8361                 first.twoMachinesColor = second.twoMachinesColor;
8362                 second.twoMachinesColor = tmp;
8363             }
8364             gameMode = nextGameMode;
8365             matchGame++;
8366             if(appData.matchPause>10000 || appData.matchPause<10)
8367                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8368             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8369             endingGame = 0; /* [HGM] crash */
8370             return;
8371         } else {
8372             char buf[MSG_SIZ];
8373             gameMode = nextGameMode;
8374             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8375                     first.tidy, second.tidy,
8376                     first.matchWins, second.matchWins,
8377                     appData.matchGames - (first.matchWins + second.matchWins));
8378             DisplayFatalError(buf, 0, 0);
8379         }
8380     }
8381     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8382         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8383       ExitAnalyzeMode();
8384     gameMode = nextGameMode;
8385     ModeHighlight();
8386     endingGame = 0;  /* [HGM] crash */
8387 }
8388
8389 /* Assumes program was just initialized (initString sent).
8390    Leaves program in force mode. */
8391 void
8392 FeedMovesToProgram(cps, upto) 
8393      ChessProgramState *cps;
8394      int upto;
8395 {
8396     int i;
8397     
8398     if (appData.debugMode)
8399       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8400               startedFromSetupPosition ? "position and " : "",
8401               backwardMostMove, upto, cps->which);
8402     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8403         // [HGM] variantswitch: make engine aware of new variant
8404         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8405                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8406         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8407         SendToProgram(buf, cps);
8408         currentlyInitializedVariant = gameInfo.variant;
8409     }
8410     SendToProgram("force\n", cps);
8411     if (startedFromSetupPosition) {
8412         SendBoard(cps, backwardMostMove);
8413     if (appData.debugMode) {
8414         fprintf(debugFP, "feedMoves\n");
8415     }
8416     }
8417     for (i = backwardMostMove; i < upto; i++) {
8418         SendMoveToProgram(i, cps);
8419     }
8420 }
8421
8422
8423 void
8424 ResurrectChessProgram()
8425 {
8426      /* The chess program may have exited.
8427         If so, restart it and feed it all the moves made so far. */
8428
8429     if (appData.noChessProgram || first.pr != NoProc) return;
8430     
8431     StartChessProgram(&first);
8432     InitChessProgram(&first, FALSE);
8433     FeedMovesToProgram(&first, currentMove);
8434
8435     if (!first.sendTime) {
8436         /* can't tell gnuchess what its clock should read,
8437            so we bow to its notion. */
8438         ResetClocks();
8439         timeRemaining[0][currentMove] = whiteTimeRemaining;
8440         timeRemaining[1][currentMove] = blackTimeRemaining;
8441     }
8442
8443     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8444                 appData.icsEngineAnalyze) && first.analysisSupport) {
8445       SendToProgram("analyze\n", &first);
8446       first.analyzing = TRUE;
8447     }
8448 }
8449
8450 /*
8451  * Button procedures
8452  */
8453 void
8454 Reset(redraw, init)
8455      int redraw, init;
8456 {
8457     int i;
8458
8459     if (appData.debugMode) {
8460         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8461                 redraw, init, gameMode);
8462     }
8463     pausing = pauseExamInvalid = FALSE;
8464     startedFromSetupPosition = blackPlaysFirst = FALSE;
8465     firstMove = TRUE;
8466     whiteFlag = blackFlag = FALSE;
8467     userOfferedDraw = FALSE;
8468     hintRequested = bookRequested = FALSE;
8469     first.maybeThinking = FALSE;
8470     second.maybeThinking = FALSE;
8471     first.bookSuspend = FALSE; // [HGM] book
8472     second.bookSuspend = FALSE;
8473     thinkOutput[0] = NULLCHAR;
8474     lastHint[0] = NULLCHAR;
8475     ClearGameInfo(&gameInfo);
8476     gameInfo.variant = StringToVariant(appData.variant);
8477     ics_user_moved = ics_clock_paused = FALSE;
8478     ics_getting_history = H_FALSE;
8479     ics_gamenum = -1;
8480     white_holding[0] = black_holding[0] = NULLCHAR;
8481     ClearProgramStats();
8482     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8483     
8484     ResetFrontEnd();
8485     ClearHighlights();
8486     flipView = appData.flipView;
8487     ClearPremoveHighlights();
8488     gotPremove = FALSE;
8489     alarmSounded = FALSE;
8490
8491     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8492     if(appData.serverMovesName != NULL) {
8493         /* [HGM] prepare to make moves file for broadcasting */
8494         clock_t t = clock();
8495         if(serverMoves != NULL) fclose(serverMoves);
8496         serverMoves = fopen(appData.serverMovesName, "r");
8497         if(serverMoves != NULL) {
8498             fclose(serverMoves);
8499             /* delay 15 sec before overwriting, so all clients can see end */
8500             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8501         }
8502         serverMoves = fopen(appData.serverMovesName, "w");
8503     }
8504
8505     ExitAnalyzeMode();
8506     gameMode = BeginningOfGame;
8507     ModeHighlight();
8508     if(appData.icsActive) gameInfo.variant = VariantNormal;
8509     currentMove = forwardMostMove = backwardMostMove = 0;
8510     InitPosition(redraw);
8511     for (i = 0; i < MAX_MOVES; i++) {
8512         if (commentList[i] != NULL) {
8513             free(commentList[i]);
8514             commentList[i] = NULL;
8515         }
8516     }
8517     ResetClocks();
8518     timeRemaining[0][0] = whiteTimeRemaining;
8519     timeRemaining[1][0] = blackTimeRemaining;
8520     if (first.pr == NULL) {
8521         StartChessProgram(&first);
8522     }
8523     if (init) {
8524             InitChessProgram(&first, startedFromSetupPosition);
8525     }
8526     DisplayTitle("");
8527     DisplayMessage("", "");
8528     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8529     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8530 }
8531
8532 void
8533 AutoPlayGameLoop()
8534 {
8535     for (;;) {
8536         if (!AutoPlayOneMove())
8537           return;
8538         if (matchMode || appData.timeDelay == 0)
8539           continue;
8540         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8541           return;
8542         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8543         break;
8544     }
8545 }
8546
8547
8548 int
8549 AutoPlayOneMove()
8550 {
8551     int fromX, fromY, toX, toY;
8552
8553     if (appData.debugMode) {
8554       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8555     }
8556
8557     if (gameMode != PlayFromGameFile)
8558       return FALSE;
8559
8560     if (currentMove >= forwardMostMove) {
8561       gameMode = EditGame;
8562       ModeHighlight();
8563
8564       /* [AS] Clear current move marker at the end of a game */
8565       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8566
8567       return FALSE;
8568     }
8569     
8570     toX = moveList[currentMove][2] - AAA;
8571     toY = moveList[currentMove][3] - ONE;
8572
8573     if (moveList[currentMove][1] == '@') {
8574         if (appData.highlightLastMove) {
8575             SetHighlights(-1, -1, toX, toY);
8576         }
8577     } else {
8578         fromX = moveList[currentMove][0] - AAA;
8579         fromY = moveList[currentMove][1] - ONE;
8580
8581         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8582
8583         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8584
8585         if (appData.highlightLastMove) {
8586             SetHighlights(fromX, fromY, toX, toY);
8587         }
8588     }
8589     DisplayMove(currentMove);
8590     SendMoveToProgram(currentMove++, &first);
8591     DisplayBothClocks();
8592     DrawPosition(FALSE, boards[currentMove]);
8593     // [HGM] PV info: always display, routine tests if empty
8594     DisplayComment(currentMove - 1, commentList[currentMove]);
8595     return TRUE;
8596 }
8597
8598
8599 int
8600 LoadGameOneMove(readAhead)
8601      ChessMove readAhead;
8602 {
8603     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8604     char promoChar = NULLCHAR;
8605     ChessMove moveType;
8606     char move[MSG_SIZ];
8607     char *p, *q;
8608     
8609     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8610         gameMode != AnalyzeMode && gameMode != Training) {
8611         gameFileFP = NULL;
8612         return FALSE;
8613     }
8614     
8615     yyboardindex = forwardMostMove;
8616     if (readAhead != (ChessMove)0) {
8617       moveType = readAhead;
8618     } else {
8619       if (gameFileFP == NULL)
8620           return FALSE;
8621       moveType = (ChessMove) yylex();
8622     }
8623     
8624     done = FALSE;
8625     switch (moveType) {
8626       case Comment:
8627         if (appData.debugMode) 
8628           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8629         p = yy_text;
8630         if (*p == '{' || *p == '[' || *p == '(') {
8631             p[strlen(p) - 1] = NULLCHAR;
8632             p++;
8633         }
8634
8635         /* append the comment but don't display it */
8636         while (*p == '\n') p++;
8637         AppendComment(currentMove, p);
8638         return TRUE;
8639
8640       case WhiteCapturesEnPassant:
8641       case BlackCapturesEnPassant:
8642       case WhitePromotionChancellor:
8643       case BlackPromotionChancellor:
8644       case WhitePromotionArchbishop:
8645       case BlackPromotionArchbishop:
8646       case WhitePromotionCentaur:
8647       case BlackPromotionCentaur:
8648       case WhitePromotionQueen:
8649       case BlackPromotionQueen:
8650       case WhitePromotionRook:
8651       case BlackPromotionRook:
8652       case WhitePromotionBishop:
8653       case BlackPromotionBishop:
8654       case WhitePromotionKnight:
8655       case BlackPromotionKnight:
8656       case WhitePromotionKing:
8657       case BlackPromotionKing:
8658       case NormalMove:
8659       case WhiteKingSideCastle:
8660       case WhiteQueenSideCastle:
8661       case BlackKingSideCastle:
8662       case BlackQueenSideCastle:
8663       case WhiteKingSideCastleWild:
8664       case WhiteQueenSideCastleWild:
8665       case BlackKingSideCastleWild:
8666       case BlackQueenSideCastleWild:
8667       /* PUSH Fabien */
8668       case WhiteHSideCastleFR:
8669       case WhiteASideCastleFR:
8670       case BlackHSideCastleFR:
8671       case BlackASideCastleFR:
8672       /* POP Fabien */
8673         if (appData.debugMode)
8674           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8675         fromX = currentMoveString[0] - AAA;
8676         fromY = currentMoveString[1] - ONE;
8677         toX = currentMoveString[2] - AAA;
8678         toY = currentMoveString[3] - ONE;
8679         promoChar = currentMoveString[4];
8680         break;
8681
8682       case WhiteDrop:
8683       case BlackDrop:
8684         if (appData.debugMode)
8685           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8686         fromX = moveType == WhiteDrop ?
8687           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8688         (int) CharToPiece(ToLower(currentMoveString[0]));
8689         fromY = DROP_RANK;
8690         toX = currentMoveString[2] - AAA;
8691         toY = currentMoveString[3] - ONE;
8692         break;
8693
8694       case WhiteWins:
8695       case BlackWins:
8696       case GameIsDrawn:
8697       case GameUnfinished:
8698         if (appData.debugMode)
8699           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8700         p = strchr(yy_text, '{');
8701         if (p == NULL) p = strchr(yy_text, '(');
8702         if (p == NULL) {
8703             p = yy_text;
8704             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8705         } else {
8706             q = strchr(p, *p == '{' ? '}' : ')');
8707             if (q != NULL) *q = NULLCHAR;
8708             p++;
8709         }
8710         GameEnds(moveType, p, GE_FILE);
8711         done = TRUE;
8712         if (cmailMsgLoaded) {
8713             ClearHighlights();
8714             flipView = WhiteOnMove(currentMove);
8715             if (moveType == GameUnfinished) flipView = !flipView;
8716             if (appData.debugMode)
8717               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8718         }
8719         break;
8720
8721       case (ChessMove) 0:       /* end of file */
8722         if (appData.debugMode)
8723           fprintf(debugFP, "Parser hit end of file\n");
8724         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8725                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8726           case MT_NONE:
8727           case MT_CHECK:
8728             break;
8729           case MT_CHECKMATE:
8730           case MT_STAINMATE:
8731             if (WhiteOnMove(currentMove)) {
8732                 GameEnds(BlackWins, "Black mates", GE_FILE);
8733             } else {
8734                 GameEnds(WhiteWins, "White mates", GE_FILE);
8735             }
8736             break;
8737           case MT_STALEMATE:
8738             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8739             break;
8740         }
8741         done = TRUE;
8742         break;
8743
8744       case MoveNumberOne:
8745         if (lastLoadGameStart == GNUChessGame) {
8746             /* GNUChessGames have numbers, but they aren't move numbers */
8747             if (appData.debugMode)
8748               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8749                       yy_text, (int) moveType);
8750             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8751         }
8752         /* else fall thru */
8753
8754       case XBoardGame:
8755       case GNUChessGame:
8756       case PGNTag:
8757         /* Reached start of next game in file */
8758         if (appData.debugMode)
8759           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8760         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8761                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8762           case MT_NONE:
8763           case MT_CHECK:
8764             break;
8765           case MT_CHECKMATE:
8766           case MT_STAINMATE:
8767             if (WhiteOnMove(currentMove)) {
8768                 GameEnds(BlackWins, "Black mates", GE_FILE);
8769             } else {
8770                 GameEnds(WhiteWins, "White mates", GE_FILE);
8771             }
8772             break;
8773           case MT_STALEMATE:
8774             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8775             break;
8776         }
8777         done = TRUE;
8778         break;
8779
8780       case PositionDiagram:     /* should not happen; ignore */
8781       case ElapsedTime:         /* ignore */
8782       case NAG:                 /* ignore */
8783         if (appData.debugMode)
8784           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8785                   yy_text, (int) moveType);
8786         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8787
8788       case IllegalMove:
8789         if (appData.testLegality) {
8790             if (appData.debugMode)
8791               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8792             sprintf(move, _("Illegal move: %d.%s%s"),
8793                     (forwardMostMove / 2) + 1,
8794                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8795             DisplayError(move, 0);
8796             done = TRUE;
8797         } else {
8798             if (appData.debugMode)
8799               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8800                       yy_text, currentMoveString);
8801             fromX = currentMoveString[0] - AAA;
8802             fromY = currentMoveString[1] - ONE;
8803             toX = currentMoveString[2] - AAA;
8804             toY = currentMoveString[3] - ONE;
8805             promoChar = currentMoveString[4];
8806         }
8807         break;
8808
8809       case AmbiguousMove:
8810         if (appData.debugMode)
8811           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8812         sprintf(move, _("Ambiguous move: %d.%s%s"),
8813                 (forwardMostMove / 2) + 1,
8814                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8815         DisplayError(move, 0);
8816         done = TRUE;
8817         break;
8818
8819       default:
8820       case ImpossibleMove:
8821         if (appData.debugMode)
8822           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8823         sprintf(move, _("Illegal move: %d.%s%s"),
8824                 (forwardMostMove / 2) + 1,
8825                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8826         DisplayError(move, 0);
8827         done = TRUE;
8828         break;
8829     }
8830
8831     if (done) {
8832         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8833             DrawPosition(FALSE, boards[currentMove]);
8834             DisplayBothClocks();
8835             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8836               DisplayComment(currentMove - 1, commentList[currentMove]);
8837         }
8838         (void) StopLoadGameTimer();
8839         gameFileFP = NULL;
8840         cmailOldMove = forwardMostMove;
8841         return FALSE;
8842     } else {
8843         /* currentMoveString is set as a side-effect of yylex */
8844         strcat(currentMoveString, "\n");
8845         strcpy(moveList[forwardMostMove], currentMoveString);
8846         
8847         thinkOutput[0] = NULLCHAR;
8848         MakeMove(fromX, fromY, toX, toY, promoChar);
8849         currentMove = forwardMostMove;
8850         return TRUE;
8851     }
8852 }
8853
8854 /* Load the nth game from the given file */
8855 int
8856 LoadGameFromFile(filename, n, title, useList)
8857      char *filename;
8858      int n;
8859      char *title;
8860      /*Boolean*/ int useList;
8861 {
8862     FILE *f;
8863     char buf[MSG_SIZ];
8864
8865     if (strcmp(filename, "-") == 0) {
8866         f = stdin;
8867         title = "stdin";
8868     } else {
8869         f = fopen(filename, "rb");
8870         if (f == NULL) {
8871           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8872             DisplayError(buf, errno);
8873             return FALSE;
8874         }
8875     }
8876     if (fseek(f, 0, 0) == -1) {
8877         /* f is not seekable; probably a pipe */
8878         useList = FALSE;
8879     }
8880     if (useList && n == 0) {
8881         int error = GameListBuild(f);
8882         if (error) {
8883             DisplayError(_("Cannot build game list"), error);
8884         } else if (!ListEmpty(&gameList) &&
8885                    ((ListGame *) gameList.tailPred)->number > 1) {
8886             GameListPopUp(f, title);
8887             return TRUE;
8888         }
8889         GameListDestroy();
8890         n = 1;
8891     }
8892     if (n == 0) n = 1;
8893     return LoadGame(f, n, title, FALSE);
8894 }
8895
8896
8897 void
8898 MakeRegisteredMove()
8899 {
8900     int fromX, fromY, toX, toY;
8901     char promoChar;
8902     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8903         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8904           case CMAIL_MOVE:
8905           case CMAIL_DRAW:
8906             if (appData.debugMode)
8907               fprintf(debugFP, "Restoring %s for game %d\n",
8908                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8909     
8910             thinkOutput[0] = NULLCHAR;
8911             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8912             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8913             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8914             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8915             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8916             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8917             MakeMove(fromX, fromY, toX, toY, promoChar);
8918             ShowMove(fromX, fromY, toX, toY);
8919               
8920             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8921                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8922               case MT_NONE:
8923               case MT_CHECK:
8924                 break;
8925                 
8926               case MT_CHECKMATE:
8927               case MT_STAINMATE:
8928                 if (WhiteOnMove(currentMove)) {
8929                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8930                 } else {
8931                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8932                 }
8933                 break;
8934                 
8935               case MT_STALEMATE:
8936                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8937                 break;
8938             }
8939
8940             break;
8941             
8942           case CMAIL_RESIGN:
8943             if (WhiteOnMove(currentMove)) {
8944                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8945             } else {
8946                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8947             }
8948             break;
8949             
8950           case CMAIL_ACCEPT:
8951             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8952             break;
8953               
8954           default:
8955             break;
8956         }
8957     }
8958
8959     return;
8960 }
8961
8962 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8963 int
8964 CmailLoadGame(f, gameNumber, title, useList)
8965      FILE *f;
8966      int gameNumber;
8967      char *title;
8968      int useList;
8969 {
8970     int retVal;
8971
8972     if (gameNumber > nCmailGames) {
8973         DisplayError(_("No more games in this message"), 0);
8974         return FALSE;
8975     }
8976     if (f == lastLoadGameFP) {
8977         int offset = gameNumber - lastLoadGameNumber;
8978         if (offset == 0) {
8979             cmailMsg[0] = NULLCHAR;
8980             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8981                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8982                 nCmailMovesRegistered--;
8983             }
8984             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8985             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8986                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8987             }
8988         } else {
8989             if (! RegisterMove()) return FALSE;
8990         }
8991     }
8992
8993     retVal = LoadGame(f, gameNumber, title, useList);
8994
8995     /* Make move registered during previous look at this game, if any */
8996     MakeRegisteredMove();
8997
8998     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8999         commentList[currentMove]
9000           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9001         DisplayComment(currentMove - 1, commentList[currentMove]);
9002     }
9003
9004     return retVal;
9005 }
9006
9007 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9008 int
9009 ReloadGame(offset)
9010      int offset;
9011 {
9012     int gameNumber = lastLoadGameNumber + offset;
9013     if (lastLoadGameFP == NULL) {
9014         DisplayError(_("No game has been loaded yet"), 0);
9015         return FALSE;
9016     }
9017     if (gameNumber <= 0) {
9018         DisplayError(_("Can't back up any further"), 0);
9019         return FALSE;
9020     }
9021     if (cmailMsgLoaded) {
9022         return CmailLoadGame(lastLoadGameFP, gameNumber,
9023                              lastLoadGameTitle, lastLoadGameUseList);
9024     } else {
9025         return LoadGame(lastLoadGameFP, gameNumber,
9026                         lastLoadGameTitle, lastLoadGameUseList);
9027     }
9028 }
9029
9030
9031
9032 /* Load the nth game from open file f */
9033 int
9034 LoadGame(f, gameNumber, title, useList)
9035      FILE *f;
9036      int gameNumber;
9037      char *title;
9038      int useList;
9039 {
9040     ChessMove cm;
9041     char buf[MSG_SIZ];
9042     int gn = gameNumber;
9043     ListGame *lg = NULL;
9044     int numPGNTags = 0;
9045     int err;
9046     GameMode oldGameMode;
9047     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9048
9049     if (appData.debugMode) 
9050         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9051
9052     if (gameMode == Training )
9053         SetTrainingModeOff();
9054
9055     oldGameMode = gameMode;
9056     if (gameMode != BeginningOfGame) {
9057       Reset(FALSE, TRUE);
9058     }
9059
9060     gameFileFP = f;
9061     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9062         fclose(lastLoadGameFP);
9063     }
9064
9065     if (useList) {
9066         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9067         
9068         if (lg) {
9069             fseek(f, lg->offset, 0);
9070             GameListHighlight(gameNumber);
9071             gn = 1;
9072         }
9073         else {
9074             DisplayError(_("Game number out of range"), 0);
9075             return FALSE;
9076         }
9077     } else {
9078         GameListDestroy();
9079         if (fseek(f, 0, 0) == -1) {
9080             if (f == lastLoadGameFP ?
9081                 gameNumber == lastLoadGameNumber + 1 :
9082                 gameNumber == 1) {
9083                 gn = 1;
9084             } else {
9085                 DisplayError(_("Can't seek on game file"), 0);
9086                 return FALSE;
9087             }
9088         }
9089     }
9090     lastLoadGameFP = f;
9091     lastLoadGameNumber = gameNumber;
9092     strcpy(lastLoadGameTitle, title);
9093     lastLoadGameUseList = useList;
9094
9095     yynewfile(f);
9096
9097     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9098       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9099                 lg->gameInfo.black);
9100             DisplayTitle(buf);
9101     } else if (*title != NULLCHAR) {
9102         if (gameNumber > 1) {
9103             sprintf(buf, "%s %d", title, gameNumber);
9104             DisplayTitle(buf);
9105         } else {
9106             DisplayTitle(title);
9107         }
9108     }
9109
9110     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9111         gameMode = PlayFromGameFile;
9112         ModeHighlight();
9113     }
9114
9115     currentMove = forwardMostMove = backwardMostMove = 0;
9116     CopyBoard(boards[0], initialPosition);
9117     StopClocks();
9118
9119     /*
9120      * Skip the first gn-1 games in the file.
9121      * Also skip over anything that precedes an identifiable 
9122      * start of game marker, to avoid being confused by 
9123      * garbage at the start of the file.  Currently 
9124      * recognized start of game markers are the move number "1",
9125      * the pattern "gnuchess .* game", the pattern
9126      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9127      * A game that starts with one of the latter two patterns
9128      * will also have a move number 1, possibly
9129      * following a position diagram.
9130      * 5-4-02: Let's try being more lenient and allowing a game to
9131      * start with an unnumbered move.  Does that break anything?
9132      */
9133     cm = lastLoadGameStart = (ChessMove) 0;
9134     while (gn > 0) {
9135         yyboardindex = forwardMostMove;
9136         cm = (ChessMove) yylex();
9137         switch (cm) {
9138           case (ChessMove) 0:
9139             if (cmailMsgLoaded) {
9140                 nCmailGames = CMAIL_MAX_GAMES - gn;
9141             } else {
9142                 Reset(TRUE, TRUE);
9143                 DisplayError(_("Game not found in file"), 0);
9144             }
9145             return FALSE;
9146
9147           case GNUChessGame:
9148           case XBoardGame:
9149             gn--;
9150             lastLoadGameStart = cm;
9151             break;
9152             
9153           case MoveNumberOne:
9154             switch (lastLoadGameStart) {
9155               case GNUChessGame:
9156               case XBoardGame:
9157               case PGNTag:
9158                 break;
9159               case MoveNumberOne:
9160               case (ChessMove) 0:
9161                 gn--;           /* count this game */
9162                 lastLoadGameStart = cm;
9163                 break;
9164               default:
9165                 /* impossible */
9166                 break;
9167             }
9168             break;
9169
9170           case PGNTag:
9171             switch (lastLoadGameStart) {
9172               case GNUChessGame:
9173               case PGNTag:
9174               case MoveNumberOne:
9175               case (ChessMove) 0:
9176                 gn--;           /* count this game */
9177                 lastLoadGameStart = cm;
9178                 break;
9179               case XBoardGame:
9180                 lastLoadGameStart = cm; /* game counted already */
9181                 break;
9182               default:
9183                 /* impossible */
9184                 break;
9185             }
9186             if (gn > 0) {
9187                 do {
9188                     yyboardindex = forwardMostMove;
9189                     cm = (ChessMove) yylex();
9190                 } while (cm == PGNTag || cm == Comment);
9191             }
9192             break;
9193
9194           case WhiteWins:
9195           case BlackWins:
9196           case GameIsDrawn:
9197             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9198                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9199                     != CMAIL_OLD_RESULT) {
9200                     nCmailResults ++ ;
9201                     cmailResult[  CMAIL_MAX_GAMES
9202                                 - gn - 1] = CMAIL_OLD_RESULT;
9203                 }
9204             }
9205             break;
9206
9207           case NormalMove:
9208             /* Only a NormalMove can be at the start of a game
9209              * without a position diagram. */
9210             if (lastLoadGameStart == (ChessMove) 0) {
9211               gn--;
9212               lastLoadGameStart = MoveNumberOne;
9213             }
9214             break;
9215
9216           default:
9217             break;
9218         }
9219     }
9220     
9221     if (appData.debugMode)
9222       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9223
9224     if (cm == XBoardGame) {
9225         /* Skip any header junk before position diagram and/or move 1 */
9226         for (;;) {
9227             yyboardindex = forwardMostMove;
9228             cm = (ChessMove) yylex();
9229
9230             if (cm == (ChessMove) 0 ||
9231                 cm == GNUChessGame || cm == XBoardGame) {
9232                 /* Empty game; pretend end-of-file and handle later */
9233                 cm = (ChessMove) 0;
9234                 break;
9235             }
9236
9237             if (cm == MoveNumberOne || cm == PositionDiagram ||
9238                 cm == PGNTag || cm == Comment)
9239               break;
9240         }
9241     } else if (cm == GNUChessGame) {
9242         if (gameInfo.event != NULL) {
9243             free(gameInfo.event);
9244         }
9245         gameInfo.event = StrSave(yy_text);
9246     }   
9247
9248     startedFromSetupPosition = FALSE;
9249     while (cm == PGNTag) {
9250         if (appData.debugMode) 
9251           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9252         err = ParsePGNTag(yy_text, &gameInfo);
9253         if (!err) numPGNTags++;
9254
9255         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9256         if(gameInfo.variant != oldVariant) {
9257             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9258             InitPosition(TRUE);
9259             oldVariant = gameInfo.variant;
9260             if (appData.debugMode) 
9261               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9262         }
9263
9264
9265         if (gameInfo.fen != NULL) {
9266           Board initial_position;
9267           startedFromSetupPosition = TRUE;
9268           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9269             Reset(TRUE, TRUE);
9270             DisplayError(_("Bad FEN position in file"), 0);
9271             return FALSE;
9272           }
9273           CopyBoard(boards[0], initial_position);
9274           if (blackPlaysFirst) {
9275             currentMove = forwardMostMove = backwardMostMove = 1;
9276             CopyBoard(boards[1], initial_position);
9277             strcpy(moveList[0], "");
9278             strcpy(parseList[0], "");
9279             timeRemaining[0][1] = whiteTimeRemaining;
9280             timeRemaining[1][1] = blackTimeRemaining;
9281             if (commentList[0] != NULL) {
9282               commentList[1] = commentList[0];
9283               commentList[0] = NULL;
9284             }
9285           } else {
9286             currentMove = forwardMostMove = backwardMostMove = 0;
9287           }
9288           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9289           {   int i;
9290               initialRulePlies = FENrulePlies;
9291               epStatus[forwardMostMove] = FENepStatus;
9292               for( i=0; i< nrCastlingRights; i++ )
9293                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9294           }
9295           yyboardindex = forwardMostMove;
9296           free(gameInfo.fen);
9297           gameInfo.fen = NULL;
9298         }
9299
9300         yyboardindex = forwardMostMove;
9301         cm = (ChessMove) yylex();
9302
9303         /* Handle comments interspersed among the tags */
9304         while (cm == Comment) {
9305             char *p;
9306             if (appData.debugMode) 
9307               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9308             p = yy_text;
9309             if (*p == '{' || *p == '[' || *p == '(') {
9310                 p[strlen(p) - 1] = NULLCHAR;
9311                 p++;
9312             }
9313             while (*p == '\n') p++;
9314             AppendComment(currentMove, p);
9315             yyboardindex = forwardMostMove;
9316             cm = (ChessMove) yylex();
9317         }
9318     }
9319
9320     /* don't rely on existence of Event tag since if game was
9321      * pasted from clipboard the Event tag may not exist
9322      */
9323     if (numPGNTags > 0){
9324         char *tags;
9325         if (gameInfo.variant == VariantNormal) {
9326           gameInfo.variant = StringToVariant(gameInfo.event);
9327         }
9328         if (!matchMode) {
9329           if( appData.autoDisplayTags ) {
9330             tags = PGNTags(&gameInfo);
9331             TagsPopUp(tags, CmailMsg());
9332             free(tags);
9333           }
9334         }
9335     } else {
9336         /* Make something up, but don't display it now */
9337         SetGameInfo();
9338         TagsPopDown();
9339     }
9340
9341     if (cm == PositionDiagram) {
9342         int i, j;
9343         char *p;
9344         Board initial_position;
9345
9346         if (appData.debugMode)
9347           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9348
9349         if (!startedFromSetupPosition) {
9350             p = yy_text;
9351             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9352               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9353                 switch (*p) {
9354                   case '[':
9355                   case '-':
9356                   case ' ':
9357                   case '\t':
9358                   case '\n':
9359                   case '\r':
9360                     break;
9361                   default:
9362                     initial_position[i][j++] = CharToPiece(*p);
9363                     break;
9364                 }
9365             while (*p == ' ' || *p == '\t' ||
9366                    *p == '\n' || *p == '\r') p++;
9367         
9368             if (strncmp(p, "black", strlen("black"))==0)
9369               blackPlaysFirst = TRUE;
9370             else
9371               blackPlaysFirst = FALSE;
9372             startedFromSetupPosition = TRUE;
9373         
9374             CopyBoard(boards[0], initial_position);
9375             if (blackPlaysFirst) {
9376                 currentMove = forwardMostMove = backwardMostMove = 1;
9377                 CopyBoard(boards[1], initial_position);
9378                 strcpy(moveList[0], "");
9379                 strcpy(parseList[0], "");
9380                 timeRemaining[0][1] = whiteTimeRemaining;
9381                 timeRemaining[1][1] = blackTimeRemaining;
9382                 if (commentList[0] != NULL) {
9383                     commentList[1] = commentList[0];
9384                     commentList[0] = NULL;
9385                 }
9386             } else {
9387                 currentMove = forwardMostMove = backwardMostMove = 0;
9388             }
9389         }
9390         yyboardindex = forwardMostMove;
9391         cm = (ChessMove) yylex();
9392     }
9393
9394     if (first.pr == NoProc) {
9395         StartChessProgram(&first);
9396     }
9397     InitChessProgram(&first, FALSE);
9398     SendToProgram("force\n", &first);
9399     if (startedFromSetupPosition) {
9400         SendBoard(&first, forwardMostMove);
9401     if (appData.debugMode) {
9402         fprintf(debugFP, "Load Game\n");
9403     }
9404         DisplayBothClocks();
9405     }      
9406
9407     /* [HGM] server: flag to write setup moves in broadcast file as one */
9408     loadFlag = appData.suppressLoadMoves;
9409
9410     while (cm == Comment) {
9411         char *p;
9412         if (appData.debugMode) 
9413           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9414         p = yy_text;
9415         if (*p == '{' || *p == '[' || *p == '(') {
9416             p[strlen(p) - 1] = NULLCHAR;
9417             p++;
9418         }
9419         while (*p == '\n') p++;
9420         AppendComment(currentMove, p);
9421         yyboardindex = forwardMostMove;
9422         cm = (ChessMove) yylex();
9423     }
9424
9425     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9426         cm == WhiteWins || cm == BlackWins ||
9427         cm == GameIsDrawn || cm == GameUnfinished) {
9428         DisplayMessage("", _("No moves in game"));
9429         if (cmailMsgLoaded) {
9430             if (appData.debugMode)
9431               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9432             ClearHighlights();
9433             flipView = FALSE;
9434         }
9435         DrawPosition(FALSE, boards[currentMove]);
9436         DisplayBothClocks();
9437         gameMode = EditGame;
9438         ModeHighlight();
9439         gameFileFP = NULL;
9440         cmailOldMove = 0;
9441         return TRUE;
9442     }
9443
9444     // [HGM] PV info: routine tests if comment empty
9445     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9446         DisplayComment(currentMove - 1, commentList[currentMove]);
9447     }
9448     if (!matchMode && appData.timeDelay != 0) 
9449       DrawPosition(FALSE, boards[currentMove]);
9450
9451     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9452       programStats.ok_to_send = 1;
9453     }
9454
9455     /* if the first token after the PGN tags is a move
9456      * and not move number 1, retrieve it from the parser 
9457      */
9458     if (cm != MoveNumberOne)
9459         LoadGameOneMove(cm);
9460
9461     /* load the remaining moves from the file */
9462     while (LoadGameOneMove((ChessMove)0)) {
9463       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9464       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9465     }
9466
9467     /* rewind to the start of the game */
9468     currentMove = backwardMostMove;
9469
9470     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9471
9472     if (oldGameMode == AnalyzeFile ||
9473         oldGameMode == AnalyzeMode) {
9474       AnalyzeFileEvent();
9475     }
9476
9477     if (matchMode || appData.timeDelay == 0) {
9478       ToEndEvent();
9479       gameMode = EditGame;
9480       ModeHighlight();
9481     } else if (appData.timeDelay > 0) {
9482       AutoPlayGameLoop();
9483     }
9484
9485     if (appData.debugMode) 
9486         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9487
9488     loadFlag = 0; /* [HGM] true game starts */
9489     return TRUE;
9490 }
9491
9492 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9493 int
9494 ReloadPosition(offset)
9495      int offset;
9496 {
9497     int positionNumber = lastLoadPositionNumber + offset;
9498     if (lastLoadPositionFP == NULL) {
9499         DisplayError(_("No position has been loaded yet"), 0);
9500         return FALSE;
9501     }
9502     if (positionNumber <= 0) {
9503         DisplayError(_("Can't back up any further"), 0);
9504         return FALSE;
9505     }
9506     return LoadPosition(lastLoadPositionFP, positionNumber,
9507                         lastLoadPositionTitle);
9508 }
9509
9510 /* Load the nth position from the given file */
9511 int
9512 LoadPositionFromFile(filename, n, title)
9513      char *filename;
9514      int n;
9515      char *title;
9516 {
9517     FILE *f;
9518     char buf[MSG_SIZ];
9519
9520     if (strcmp(filename, "-") == 0) {
9521         return LoadPosition(stdin, n, "stdin");
9522     } else {
9523         f = fopen(filename, "rb");
9524         if (f == NULL) {
9525             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9526             DisplayError(buf, errno);
9527             return FALSE;
9528         } else {
9529             return LoadPosition(f, n, title);
9530         }
9531     }
9532 }
9533
9534 /* Load the nth position from the given open file, and close it */
9535 int
9536 LoadPosition(f, positionNumber, title)
9537      FILE *f;
9538      int positionNumber;
9539      char *title;
9540 {
9541     char *p, line[MSG_SIZ];
9542     Board initial_position;
9543     int i, j, fenMode, pn;
9544     
9545     if (gameMode == Training )
9546         SetTrainingModeOff();
9547
9548     if (gameMode != BeginningOfGame) {
9549         Reset(FALSE, TRUE);
9550     }
9551     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9552         fclose(lastLoadPositionFP);
9553     }
9554     if (positionNumber == 0) positionNumber = 1;
9555     lastLoadPositionFP = f;
9556     lastLoadPositionNumber = positionNumber;
9557     strcpy(lastLoadPositionTitle, title);
9558     if (first.pr == NoProc) {
9559       StartChessProgram(&first);
9560       InitChessProgram(&first, FALSE);
9561     }    
9562     pn = positionNumber;
9563     if (positionNumber < 0) {
9564         /* Negative position number means to seek to that byte offset */
9565         if (fseek(f, -positionNumber, 0) == -1) {
9566             DisplayError(_("Can't seek on position file"), 0);
9567             return FALSE;
9568         };
9569         pn = 1;
9570     } else {
9571         if (fseek(f, 0, 0) == -1) {
9572             if (f == lastLoadPositionFP ?
9573                 positionNumber == lastLoadPositionNumber + 1 :
9574                 positionNumber == 1) {
9575                 pn = 1;
9576             } else {
9577                 DisplayError(_("Can't seek on position file"), 0);
9578                 return FALSE;
9579             }
9580         }
9581     }
9582     /* See if this file is FEN or old-style xboard */
9583     if (fgets(line, MSG_SIZ, f) == NULL) {
9584         DisplayError(_("Position not found in file"), 0);
9585         return FALSE;
9586     }
9587     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9588     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9589
9590     if (pn >= 2) {
9591         if (fenMode || line[0] == '#') pn--;
9592         while (pn > 0) {
9593             /* skip positions before number pn */
9594             if (fgets(line, MSG_SIZ, f) == NULL) {
9595                 Reset(TRUE, TRUE);
9596                 DisplayError(_("Position not found in file"), 0);
9597                 return FALSE;
9598             }
9599             if (fenMode || line[0] == '#') pn--;
9600         }
9601     }
9602
9603     if (fenMode) {
9604         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9605             DisplayError(_("Bad FEN position in file"), 0);
9606             return FALSE;
9607         }
9608     } else {
9609         (void) fgets(line, MSG_SIZ, f);
9610         (void) fgets(line, MSG_SIZ, f);
9611     
9612         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9613             (void) fgets(line, MSG_SIZ, f);
9614             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9615                 if (*p == ' ')
9616                   continue;
9617                 initial_position[i][j++] = CharToPiece(*p);
9618             }
9619         }
9620     
9621         blackPlaysFirst = FALSE;
9622         if (!feof(f)) {
9623             (void) fgets(line, MSG_SIZ, f);
9624             if (strncmp(line, "black", strlen("black"))==0)
9625               blackPlaysFirst = TRUE;
9626         }
9627     }
9628     startedFromSetupPosition = TRUE;
9629     
9630     SendToProgram("force\n", &first);
9631     CopyBoard(boards[0], initial_position);
9632     if (blackPlaysFirst) {
9633         currentMove = forwardMostMove = backwardMostMove = 1;
9634         strcpy(moveList[0], "");
9635         strcpy(parseList[0], "");
9636         CopyBoard(boards[1], initial_position);
9637         DisplayMessage("", _("Black to play"));
9638     } else {
9639         currentMove = forwardMostMove = backwardMostMove = 0;
9640         DisplayMessage("", _("White to play"));
9641     }
9642           /* [HGM] copy FEN attributes as well */
9643           {   int i;
9644               initialRulePlies = FENrulePlies;
9645               epStatus[forwardMostMove] = FENepStatus;
9646               for( i=0; i< nrCastlingRights; i++ )
9647                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9648           }
9649     SendBoard(&first, forwardMostMove);
9650     if (appData.debugMode) {
9651 int i, j;
9652   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9653   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9654         fprintf(debugFP, "Load Position\n");
9655     }
9656
9657     if (positionNumber > 1) {
9658         sprintf(line, "%s %d", title, positionNumber);
9659         DisplayTitle(line);
9660     } else {
9661         DisplayTitle(title);
9662     }
9663     gameMode = EditGame;
9664     ModeHighlight();
9665     ResetClocks();
9666     timeRemaining[0][1] = whiteTimeRemaining;
9667     timeRemaining[1][1] = blackTimeRemaining;
9668     DrawPosition(FALSE, boards[currentMove]);
9669    
9670     return TRUE;
9671 }
9672
9673
9674 void
9675 CopyPlayerNameIntoFileName(dest, src)
9676      char **dest, *src;
9677 {
9678     while (*src != NULLCHAR && *src != ',') {
9679         if (*src == ' ') {
9680             *(*dest)++ = '_';
9681             src++;
9682         } else {
9683             *(*dest)++ = *src++;
9684         }
9685     }
9686 }
9687
9688 char *DefaultFileName(ext)
9689      char *ext;
9690 {
9691     static char def[MSG_SIZ];
9692     char *p;
9693
9694     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9695         p = def;
9696         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9697         *p++ = '-';
9698         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9699         *p++ = '.';
9700         strcpy(p, ext);
9701     } else {
9702         def[0] = NULLCHAR;
9703     }
9704     return def;
9705 }
9706
9707 /* Save the current game to the given file */
9708 int
9709 SaveGameToFile(filename, append)
9710      char *filename;
9711      int append;
9712 {
9713     FILE *f;
9714     char buf[MSG_SIZ];
9715
9716     if (strcmp(filename, "-") == 0) {
9717         return SaveGame(stdout, 0, NULL);
9718     } else {
9719         f = fopen(filename, append ? "a" : "w");
9720         if (f == NULL) {
9721             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9722             DisplayError(buf, errno);
9723             return FALSE;
9724         } else {
9725             return SaveGame(f, 0, NULL);
9726         }
9727     }
9728 }
9729
9730 char *
9731 SavePart(str)
9732      char *str;
9733 {
9734     static char buf[MSG_SIZ];
9735     char *p;
9736     
9737     p = strchr(str, ' ');
9738     if (p == NULL) return str;
9739     strncpy(buf, str, p - str);
9740     buf[p - str] = NULLCHAR;
9741     return buf;
9742 }
9743
9744 #define PGN_MAX_LINE 75
9745
9746 #define PGN_SIDE_WHITE  0
9747 #define PGN_SIDE_BLACK  1
9748
9749 /* [AS] */
9750 static int FindFirstMoveOutOfBook( int side )
9751 {
9752     int result = -1;
9753
9754     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9755         int index = backwardMostMove;
9756         int has_book_hit = 0;
9757
9758         if( (index % 2) != side ) {
9759             index++;
9760         }
9761
9762         while( index < forwardMostMove ) {
9763             /* Check to see if engine is in book */
9764             int depth = pvInfoList[index].depth;
9765             int score = pvInfoList[index].score;
9766             int in_book = 0;
9767
9768             if( depth <= 2 ) {
9769                 in_book = 1;
9770             }
9771             else if( score == 0 && depth == 63 ) {
9772                 in_book = 1; /* Zappa */
9773             }
9774             else if( score == 2 && depth == 99 ) {
9775                 in_book = 1; /* Abrok */
9776             }
9777
9778             has_book_hit += in_book;
9779
9780             if( ! in_book ) {
9781                 result = index;
9782
9783                 break;
9784             }
9785
9786             index += 2;
9787         }
9788     }
9789
9790     return result;
9791 }
9792
9793 /* [AS] */
9794 void GetOutOfBookInfo( char * buf )
9795 {
9796     int oob[2];
9797     int i;
9798     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9799
9800     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9801     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9802
9803     *buf = '\0';
9804
9805     if( oob[0] >= 0 || oob[1] >= 0 ) {
9806         for( i=0; i<2; i++ ) {
9807             int idx = oob[i];
9808
9809             if( idx >= 0 ) {
9810                 if( i > 0 && oob[0] >= 0 ) {
9811                     strcat( buf, "   " );
9812                 }
9813
9814                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9815                 sprintf( buf+strlen(buf), "%s%.2f", 
9816                     pvInfoList[idx].score >= 0 ? "+" : "",
9817                     pvInfoList[idx].score / 100.0 );
9818             }
9819         }
9820     }
9821 }
9822
9823 /* Save game in PGN style and close the file */
9824 int
9825 SaveGamePGN(f)
9826      FILE *f;
9827 {
9828     int i, offset, linelen, newblock;
9829     time_t tm;
9830 //    char *movetext;
9831     char numtext[32];
9832     int movelen, numlen, blank;
9833     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9834
9835     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9836     
9837     tm = time((time_t *) NULL);
9838     
9839     PrintPGNTags(f, &gameInfo);
9840     
9841     if (backwardMostMove > 0 || startedFromSetupPosition) {
9842         char *fen = PositionToFEN(backwardMostMove, NULL);
9843         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9844         fprintf(f, "\n{--------------\n");
9845         PrintPosition(f, backwardMostMove);
9846         fprintf(f, "--------------}\n");
9847         free(fen);
9848     }
9849     else {
9850         /* [AS] Out of book annotation */
9851         if( appData.saveOutOfBookInfo ) {
9852             char buf[64];
9853
9854             GetOutOfBookInfo( buf );
9855
9856             if( buf[0] != '\0' ) {
9857                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9858             }
9859         }
9860
9861         fprintf(f, "\n");
9862     }
9863
9864     i = backwardMostMove;
9865     linelen = 0;
9866     newblock = TRUE;
9867
9868     while (i < forwardMostMove) {
9869         /* Print comments preceding this move */
9870         if (commentList[i] != NULL) {
9871             if (linelen > 0) fprintf(f, "\n");
9872             fprintf(f, "{\n%s}\n", commentList[i]);
9873             linelen = 0;
9874             newblock = TRUE;
9875         }
9876
9877         /* Format move number */
9878         if ((i % 2) == 0) {
9879             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9880         } else {
9881             if (newblock) {
9882                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9883             } else {
9884                 numtext[0] = NULLCHAR;
9885             }
9886         }
9887         numlen = strlen(numtext);
9888         newblock = FALSE;
9889
9890         /* Print move number */
9891         blank = linelen > 0 && numlen > 0;
9892         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9893             fprintf(f, "\n");
9894             linelen = 0;
9895             blank = 0;
9896         }
9897         if (blank) {
9898             fprintf(f, " ");
9899             linelen++;
9900         }
9901         fprintf(f, "%s", numtext);
9902         linelen += numlen;
9903
9904         /* Get move */
9905         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9906         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9907
9908         /* Print move */
9909         blank = linelen > 0 && movelen > 0;
9910         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9911             fprintf(f, "\n");
9912             linelen = 0;
9913             blank = 0;
9914         }
9915         if (blank) {
9916             fprintf(f, " ");
9917             linelen++;
9918         }
9919         fprintf(f, "%s", move_buffer);
9920         linelen += movelen;
9921
9922         /* [AS] Add PV info if present */
9923         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9924             /* [HGM] add time */
9925             char buf[MSG_SIZ]; int seconds = 0;
9926
9927             if(i >= backwardMostMove) {
9928                 if(WhiteOnMove(i))
9929                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9930                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9931                 else
9932                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9933                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9934             }
9935             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9936
9937             if( seconds <= 0) buf[0] = 0; else
9938             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9939                 seconds = (seconds + 4)/10; // round to full seconds
9940                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9941                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9942             }
9943
9944             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9945                 pvInfoList[i].score >= 0 ? "+" : "",
9946                 pvInfoList[i].score / 100.0,
9947                 pvInfoList[i].depth,
9948                 buf );
9949
9950             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9951
9952             /* Print score/depth */
9953             blank = linelen > 0 && movelen > 0;
9954             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9955                 fprintf(f, "\n");
9956                 linelen = 0;
9957                 blank = 0;
9958             }
9959             if (blank) {
9960                 fprintf(f, " ");
9961                 linelen++;
9962             }
9963             fprintf(f, "%s", move_buffer);
9964             linelen += movelen;
9965         }
9966
9967         i++;
9968     }
9969     
9970     /* Start a new line */
9971     if (linelen > 0) fprintf(f, "\n");
9972
9973     /* Print comments after last move */
9974     if (commentList[i] != NULL) {
9975         fprintf(f, "{\n%s}\n", commentList[i]);
9976     }
9977
9978     /* Print result */
9979     if (gameInfo.resultDetails != NULL &&
9980         gameInfo.resultDetails[0] != NULLCHAR) {
9981         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9982                 PGNResult(gameInfo.result));
9983     } else {
9984         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9985     }
9986
9987     fclose(f);
9988     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9989     return TRUE;
9990 }
9991
9992 /* Save game in old style and close the file */
9993 int
9994 SaveGameOldStyle(f)
9995      FILE *f;
9996 {
9997     int i, offset;
9998     time_t tm;
9999     
10000     tm = time((time_t *) NULL);
10001     
10002     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10003     PrintOpponents(f);
10004     
10005     if (backwardMostMove > 0 || startedFromSetupPosition) {
10006         fprintf(f, "\n[--------------\n");
10007         PrintPosition(f, backwardMostMove);
10008         fprintf(f, "--------------]\n");
10009     } else {
10010         fprintf(f, "\n");
10011     }
10012
10013     i = backwardMostMove;
10014     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10015
10016     while (i < forwardMostMove) {
10017         if (commentList[i] != NULL) {
10018             fprintf(f, "[%s]\n", commentList[i]);
10019         }
10020
10021         if ((i % 2) == 1) {
10022             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10023             i++;
10024         } else {
10025             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10026             i++;
10027             if (commentList[i] != NULL) {
10028                 fprintf(f, "\n");
10029                 continue;
10030             }
10031             if (i >= forwardMostMove) {
10032                 fprintf(f, "\n");
10033                 break;
10034             }
10035             fprintf(f, "%s\n", parseList[i]);
10036             i++;
10037         }
10038     }
10039     
10040     if (commentList[i] != NULL) {
10041         fprintf(f, "[%s]\n", commentList[i]);
10042     }
10043
10044     /* This isn't really the old style, but it's close enough */
10045     if (gameInfo.resultDetails != NULL &&
10046         gameInfo.resultDetails[0] != NULLCHAR) {
10047         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10048                 gameInfo.resultDetails);
10049     } else {
10050         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10051     }
10052
10053     fclose(f);
10054     return TRUE;
10055 }
10056
10057 /* Save the current game to open file f and close the file */
10058 int
10059 SaveGame(f, dummy, dummy2)
10060      FILE *f;
10061      int dummy;
10062      char *dummy2;
10063 {
10064     if (gameMode == EditPosition) EditPositionDone();
10065     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10066     if (appData.oldSaveStyle)
10067       return SaveGameOldStyle(f);
10068     else
10069       return SaveGamePGN(f);
10070 }
10071
10072 /* Save the current position to the given file */
10073 int
10074 SavePositionToFile(filename)
10075      char *filename;
10076 {
10077     FILE *f;
10078     char buf[MSG_SIZ];
10079
10080     if (strcmp(filename, "-") == 0) {
10081         return SavePosition(stdout, 0, NULL);
10082     } else {
10083         f = fopen(filename, "a");
10084         if (f == NULL) {
10085             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10086             DisplayError(buf, errno);
10087             return FALSE;
10088         } else {
10089             SavePosition(f, 0, NULL);
10090             return TRUE;
10091         }
10092     }
10093 }
10094
10095 /* Save the current position to the given open file and close the file */
10096 int
10097 SavePosition(f, dummy, dummy2)
10098      FILE *f;
10099      int dummy;
10100      char *dummy2;
10101 {
10102     time_t tm;
10103     char *fen;
10104     
10105     if (appData.oldSaveStyle) {
10106         tm = time((time_t *) NULL);
10107     
10108         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10109         PrintOpponents(f);
10110         fprintf(f, "[--------------\n");
10111         PrintPosition(f, currentMove);
10112         fprintf(f, "--------------]\n");
10113     } else {
10114         fen = PositionToFEN(currentMove, NULL);
10115         fprintf(f, "%s\n", fen);
10116         free(fen);
10117     }
10118     fclose(f);
10119     return TRUE;
10120 }
10121
10122 void
10123 ReloadCmailMsgEvent(unregister)
10124      int unregister;
10125 {
10126 #if !WIN32
10127     static char *inFilename = NULL;
10128     static char *outFilename;
10129     int i;
10130     struct stat inbuf, outbuf;
10131     int status;
10132     
10133     /* Any registered moves are unregistered if unregister is set, */
10134     /* i.e. invoked by the signal handler */
10135     if (unregister) {
10136         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10137             cmailMoveRegistered[i] = FALSE;
10138             if (cmailCommentList[i] != NULL) {
10139                 free(cmailCommentList[i]);
10140                 cmailCommentList[i] = NULL;
10141             }
10142         }
10143         nCmailMovesRegistered = 0;
10144     }
10145
10146     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10147         cmailResult[i] = CMAIL_NOT_RESULT;
10148     }
10149     nCmailResults = 0;
10150
10151     if (inFilename == NULL) {
10152         /* Because the filenames are static they only get malloced once  */
10153         /* and they never get freed                                      */
10154         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10155         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10156
10157         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10158         sprintf(outFilename, "%s.out", appData.cmailGameName);
10159     }
10160     
10161     status = stat(outFilename, &outbuf);
10162     if (status < 0) {
10163         cmailMailedMove = FALSE;
10164     } else {
10165         status = stat(inFilename, &inbuf);
10166         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10167     }
10168     
10169     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10170        counts the games, notes how each one terminated, etc.
10171        
10172        It would be nice to remove this kludge and instead gather all
10173        the information while building the game list.  (And to keep it
10174        in the game list nodes instead of having a bunch of fixed-size
10175        parallel arrays.)  Note this will require getting each game's
10176        termination from the PGN tags, as the game list builder does
10177        not process the game moves.  --mann
10178        */
10179     cmailMsgLoaded = TRUE;
10180     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10181     
10182     /* Load first game in the file or popup game menu */
10183     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10184
10185 #endif /* !WIN32 */
10186     return;
10187 }
10188
10189 int
10190 RegisterMove()
10191 {
10192     FILE *f;
10193     char string[MSG_SIZ];
10194
10195     if (   cmailMailedMove
10196         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10197         return TRUE;            /* Allow free viewing  */
10198     }
10199
10200     /* Unregister move to ensure that we don't leave RegisterMove        */
10201     /* with the move registered when the conditions for registering no   */
10202     /* longer hold                                                       */
10203     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10204         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10205         nCmailMovesRegistered --;
10206
10207         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10208           {
10209               free(cmailCommentList[lastLoadGameNumber - 1]);
10210               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10211           }
10212     }
10213
10214     if (cmailOldMove == -1) {
10215         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10216         return FALSE;
10217     }
10218
10219     if (currentMove > cmailOldMove + 1) {
10220         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10221         return FALSE;
10222     }
10223
10224     if (currentMove < cmailOldMove) {
10225         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10226         return FALSE;
10227     }
10228
10229     if (forwardMostMove > currentMove) {
10230         /* Silently truncate extra moves */
10231         TruncateGame();
10232     }
10233
10234     if (   (currentMove == cmailOldMove + 1)
10235         || (   (currentMove == cmailOldMove)
10236             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10237                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10238         if (gameInfo.result != GameUnfinished) {
10239             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10240         }
10241
10242         if (commentList[currentMove] != NULL) {
10243             cmailCommentList[lastLoadGameNumber - 1]
10244               = StrSave(commentList[currentMove]);
10245         }
10246         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10247
10248         if (appData.debugMode)
10249           fprintf(debugFP, "Saving %s for game %d\n",
10250                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10251
10252         sprintf(string,
10253                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10254         
10255         f = fopen(string, "w");
10256         if (appData.oldSaveStyle) {
10257             SaveGameOldStyle(f); /* also closes the file */
10258             
10259             sprintf(string, "%s.pos.out", appData.cmailGameName);
10260             f = fopen(string, "w");
10261             SavePosition(f, 0, NULL); /* also closes the file */
10262         } else {
10263             fprintf(f, "{--------------\n");
10264             PrintPosition(f, currentMove);
10265             fprintf(f, "--------------}\n\n");
10266             
10267             SaveGame(f, 0, NULL); /* also closes the file*/
10268         }
10269         
10270         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10271         nCmailMovesRegistered ++;
10272     } else if (nCmailGames == 1) {
10273         DisplayError(_("You have not made a move yet"), 0);
10274         return FALSE;
10275     }
10276
10277     return TRUE;
10278 }
10279
10280 void
10281 MailMoveEvent()
10282 {
10283 #if !WIN32
10284     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10285     FILE *commandOutput;
10286     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10287     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10288     int nBuffers;
10289     int i;
10290     int archived;
10291     char *arcDir;
10292
10293     if (! cmailMsgLoaded) {
10294         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10295         return;
10296     }
10297
10298     if (nCmailGames == nCmailResults) {
10299         DisplayError(_("No unfinished games"), 0);
10300         return;
10301     }
10302
10303 #if CMAIL_PROHIBIT_REMAIL
10304     if (cmailMailedMove) {
10305         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);
10306         DisplayError(msg, 0);
10307         return;
10308     }
10309 #endif
10310
10311     if (! (cmailMailedMove || RegisterMove())) return;
10312     
10313     if (   cmailMailedMove
10314         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10315         sprintf(string, partCommandString,
10316                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10317         commandOutput = popen(string, "r");
10318
10319         if (commandOutput == NULL) {
10320             DisplayError(_("Failed to invoke cmail"), 0);
10321         } else {
10322             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10323                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10324             }
10325             if (nBuffers > 1) {
10326                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10327                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10328                 nBytes = MSG_SIZ - 1;
10329             } else {
10330                 (void) memcpy(msg, buffer, nBytes);
10331             }
10332             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10333
10334             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10335                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10336
10337                 archived = TRUE;
10338                 for (i = 0; i < nCmailGames; i ++) {
10339                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10340                         archived = FALSE;
10341                     }
10342                 }
10343                 if (   archived
10344                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10345                         != NULL)) {
10346                     sprintf(buffer, "%s/%s.%s.archive",
10347                             arcDir,
10348                             appData.cmailGameName,
10349                             gameInfo.date);
10350                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10351                     cmailMsgLoaded = FALSE;
10352                 }
10353             }
10354
10355             DisplayInformation(msg);
10356             pclose(commandOutput);
10357         }
10358     } else {
10359         if ((*cmailMsg) != '\0') {
10360             DisplayInformation(cmailMsg);
10361         }
10362     }
10363
10364     return;
10365 #endif /* !WIN32 */
10366 }
10367
10368 char *
10369 CmailMsg()
10370 {
10371 #if WIN32
10372     return NULL;
10373 #else
10374     int  prependComma = 0;
10375     char number[5];
10376     char string[MSG_SIZ];       /* Space for game-list */
10377     int  i;
10378     
10379     if (!cmailMsgLoaded) return "";
10380
10381     if (cmailMailedMove) {
10382         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10383     } else {
10384         /* Create a list of games left */
10385         sprintf(string, "[");
10386         for (i = 0; i < nCmailGames; i ++) {
10387             if (! (   cmailMoveRegistered[i]
10388                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10389                 if (prependComma) {
10390                     sprintf(number, ",%d", i + 1);
10391                 } else {
10392                     sprintf(number, "%d", i + 1);
10393                     prependComma = 1;
10394                 }
10395                 
10396                 strcat(string, number);
10397             }
10398         }
10399         strcat(string, "]");
10400
10401         if (nCmailMovesRegistered + nCmailResults == 0) {
10402             switch (nCmailGames) {
10403               case 1:
10404                 sprintf(cmailMsg,
10405                         _("Still need to make move for game\n"));
10406                 break;
10407                 
10408               case 2:
10409                 sprintf(cmailMsg,
10410                         _("Still need to make moves for both games\n"));
10411                 break;
10412                 
10413               default:
10414                 sprintf(cmailMsg,
10415                         _("Still need to make moves for all %d games\n"),
10416                         nCmailGames);
10417                 break;
10418             }
10419         } else {
10420             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10421               case 1:
10422                 sprintf(cmailMsg,
10423                         _("Still need to make a move for game %s\n"),
10424                         string);
10425                 break;
10426                 
10427               case 0:
10428                 if (nCmailResults == nCmailGames) {
10429                     sprintf(cmailMsg, _("No unfinished games\n"));
10430                 } else {
10431                     sprintf(cmailMsg, _("Ready to send mail\n"));
10432                 }
10433                 break;
10434                 
10435               default:
10436                 sprintf(cmailMsg,
10437                         _("Still need to make moves for games %s\n"),
10438                         string);
10439             }
10440         }
10441     }
10442     return cmailMsg;
10443 #endif /* WIN32 */
10444 }
10445
10446 void
10447 ResetGameEvent()
10448 {
10449     if (gameMode == Training)
10450       SetTrainingModeOff();
10451
10452     Reset(TRUE, TRUE);
10453     cmailMsgLoaded = FALSE;
10454     if (appData.icsActive) {
10455       SendToICS(ics_prefix);
10456       SendToICS("refresh\n");
10457     }
10458 }
10459
10460 void
10461 ExitEvent(status)
10462      int status;
10463 {
10464     exiting++;
10465     if (exiting > 2) {
10466       /* Give up on clean exit */
10467       exit(status);
10468     }
10469     if (exiting > 1) {
10470       /* Keep trying for clean exit */
10471       return;
10472     }
10473
10474     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10475
10476     if (telnetISR != NULL) {
10477       RemoveInputSource(telnetISR);
10478     }
10479     if (icsPR != NoProc) {
10480       DestroyChildProcess(icsPR, TRUE);
10481     }
10482
10483     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10484     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10485
10486     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10487     /* make sure this other one finishes before killing it!                  */
10488     if(endingGame) { int count = 0;
10489         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10490         while(endingGame && count++ < 10) DoSleep(1);
10491         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10492     }
10493
10494     /* Kill off chess programs */
10495     if (first.pr != NoProc) {
10496         ExitAnalyzeMode();
10497         
10498         DoSleep( appData.delayBeforeQuit );
10499         SendToProgram("quit\n", &first);
10500         DoSleep( appData.delayAfterQuit );
10501         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10502     }
10503     if (second.pr != NoProc) {
10504         DoSleep( appData.delayBeforeQuit );
10505         SendToProgram("quit\n", &second);
10506         DoSleep( appData.delayAfterQuit );
10507         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10508     }
10509     if (first.isr != NULL) {
10510         RemoveInputSource(first.isr);
10511     }
10512     if (second.isr != NULL) {
10513         RemoveInputSource(second.isr);
10514     }
10515
10516     ShutDownFrontEnd();
10517     exit(status);
10518 }
10519
10520 void
10521 PauseEvent()
10522 {
10523     if (appData.debugMode)
10524         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10525     if (pausing) {
10526         pausing = FALSE;
10527         ModeHighlight();
10528         if (gameMode == MachinePlaysWhite ||
10529             gameMode == MachinePlaysBlack) {
10530             StartClocks();
10531         } else {
10532             DisplayBothClocks();
10533         }
10534         if (gameMode == PlayFromGameFile) {
10535             if (appData.timeDelay >= 0) 
10536                 AutoPlayGameLoop();
10537         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10538             Reset(FALSE, TRUE);
10539             SendToICS(ics_prefix);
10540             SendToICS("refresh\n");
10541         } else if (currentMove < forwardMostMove) {
10542             ForwardInner(forwardMostMove);
10543         }
10544         pauseExamInvalid = FALSE;
10545     } else {
10546         switch (gameMode) {
10547           default:
10548             return;
10549           case IcsExamining:
10550             pauseExamForwardMostMove = forwardMostMove;
10551             pauseExamInvalid = FALSE;
10552             /* fall through */
10553           case IcsObserving:
10554           case IcsPlayingWhite:
10555           case IcsPlayingBlack:
10556             pausing = TRUE;
10557             ModeHighlight();
10558             return;
10559           case PlayFromGameFile:
10560             (void) StopLoadGameTimer();
10561             pausing = TRUE;
10562             ModeHighlight();
10563             break;
10564           case BeginningOfGame:
10565             if (appData.icsActive) return;
10566             /* else fall through */
10567           case MachinePlaysWhite:
10568           case MachinePlaysBlack:
10569           case TwoMachinesPlay:
10570             if (forwardMostMove == 0)
10571               return;           /* don't pause if no one has moved */
10572             if ((gameMode == MachinePlaysWhite &&
10573                  !WhiteOnMove(forwardMostMove)) ||
10574                 (gameMode == MachinePlaysBlack &&
10575                  WhiteOnMove(forwardMostMove))) {
10576                 StopClocks();
10577             }
10578             pausing = TRUE;
10579             ModeHighlight();
10580             break;
10581         }
10582     }
10583 }
10584
10585 void
10586 EditCommentEvent()
10587 {
10588     char title[MSG_SIZ];
10589
10590     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10591         strcpy(title, _("Edit comment"));
10592     } else {
10593         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10594                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10595                 parseList[currentMove - 1]);
10596     }
10597
10598     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10599 }
10600
10601
10602 void
10603 EditTagsEvent()
10604 {
10605     char *tags = PGNTags(&gameInfo);
10606     EditTagsPopUp(tags);
10607     free(tags);
10608 }
10609
10610 void
10611 AnalyzeModeEvent()
10612 {
10613     if (appData.noChessProgram || gameMode == AnalyzeMode)
10614       return;
10615
10616     if (gameMode != AnalyzeFile) {
10617         if (!appData.icsEngineAnalyze) {
10618                EditGameEvent();
10619                if (gameMode != EditGame) return;
10620         }
10621         ResurrectChessProgram();
10622         SendToProgram("analyze\n", &first);
10623         first.analyzing = TRUE;
10624         /*first.maybeThinking = TRUE;*/
10625         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10626         EngineOutputPopUp();
10627     }
10628     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10629     pausing = FALSE;
10630     ModeHighlight();
10631     SetGameInfo();
10632
10633     StartAnalysisClock();
10634     GetTimeMark(&lastNodeCountTime);
10635     lastNodeCount = 0;
10636 }
10637
10638 void
10639 AnalyzeFileEvent()
10640 {
10641     if (appData.noChessProgram || gameMode == AnalyzeFile)
10642       return;
10643
10644     if (gameMode != AnalyzeMode) {
10645         EditGameEvent();
10646         if (gameMode != EditGame) return;
10647         ResurrectChessProgram();
10648         SendToProgram("analyze\n", &first);
10649         first.analyzing = TRUE;
10650         /*first.maybeThinking = TRUE;*/
10651         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10652         EngineOutputPopUp();
10653     }
10654     gameMode = AnalyzeFile;
10655     pausing = FALSE;
10656     ModeHighlight();
10657     SetGameInfo();
10658
10659     StartAnalysisClock();
10660     GetTimeMark(&lastNodeCountTime);
10661     lastNodeCount = 0;
10662 }
10663
10664 void
10665 MachineWhiteEvent()
10666 {
10667     char buf[MSG_SIZ];
10668     char *bookHit = NULL;
10669
10670     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10671       return;
10672
10673
10674     if (gameMode == PlayFromGameFile || 
10675         gameMode == TwoMachinesPlay  || 
10676         gameMode == Training         || 
10677         gameMode == AnalyzeMode      || 
10678         gameMode == EndOfGame)
10679         EditGameEvent();
10680
10681     if (gameMode == EditPosition) 
10682         EditPositionDone();
10683
10684     if (!WhiteOnMove(currentMove)) {
10685         DisplayError(_("It is not White's turn"), 0);
10686         return;
10687     }
10688   
10689     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10690       ExitAnalyzeMode();
10691
10692     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10693         gameMode == AnalyzeFile)
10694         TruncateGame();
10695
10696     ResurrectChessProgram();    /* in case it isn't running */
10697     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10698         gameMode = MachinePlaysWhite;
10699         ResetClocks();
10700     } else
10701     gameMode = MachinePlaysWhite;
10702     pausing = FALSE;
10703     ModeHighlight();
10704     SetGameInfo();
10705     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10706     DisplayTitle(buf);
10707     if (first.sendName) {
10708       sprintf(buf, "name %s\n", gameInfo.black);
10709       SendToProgram(buf, &first);
10710     }
10711     if (first.sendTime) {
10712       if (first.useColors) {
10713         SendToProgram("black\n", &first); /*gnu kludge*/
10714       }
10715       SendTimeRemaining(&first, TRUE);
10716     }
10717     if (first.useColors) {
10718       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10719     }
10720     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10721     SetMachineThinkingEnables();
10722     first.maybeThinking = TRUE;
10723     StartClocks();
10724     firstMove = FALSE;
10725
10726     if (appData.autoFlipView && !flipView) {
10727       flipView = !flipView;
10728       DrawPosition(FALSE, NULL);
10729       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10730     }
10731
10732     if(bookHit) { // [HGM] book: simulate book reply
10733         static char bookMove[MSG_SIZ]; // a bit generous?
10734
10735         programStats.nodes = programStats.depth = programStats.time = 
10736         programStats.score = programStats.got_only_move = 0;
10737         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10738
10739         strcpy(bookMove, "move ");
10740         strcat(bookMove, bookHit);
10741         HandleMachineMove(bookMove, &first);
10742     }
10743 }
10744
10745 void
10746 MachineBlackEvent()
10747 {
10748     char buf[MSG_SIZ];
10749    char *bookHit = NULL;
10750
10751     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10752         return;
10753
10754
10755     if (gameMode == PlayFromGameFile || 
10756         gameMode == TwoMachinesPlay  || 
10757         gameMode == Training         || 
10758         gameMode == AnalyzeMode      || 
10759         gameMode == EndOfGame)
10760         EditGameEvent();
10761
10762     if (gameMode == EditPosition) 
10763         EditPositionDone();
10764
10765     if (WhiteOnMove(currentMove)) {
10766         DisplayError(_("It is not Black's turn"), 0);
10767         return;
10768     }
10769     
10770     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10771       ExitAnalyzeMode();
10772
10773     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10774         gameMode == AnalyzeFile)
10775         TruncateGame();
10776
10777     ResurrectChessProgram();    /* in case it isn't running */
10778     gameMode = MachinePlaysBlack;
10779     pausing = FALSE;
10780     ModeHighlight();
10781     SetGameInfo();
10782     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10783     DisplayTitle(buf);
10784     if (first.sendName) {
10785       sprintf(buf, "name %s\n", gameInfo.white);
10786       SendToProgram(buf, &first);
10787     }
10788     if (first.sendTime) {
10789       if (first.useColors) {
10790         SendToProgram("white\n", &first); /*gnu kludge*/
10791       }
10792       SendTimeRemaining(&first, FALSE);
10793     }
10794     if (first.useColors) {
10795       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10796     }
10797     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10798     SetMachineThinkingEnables();
10799     first.maybeThinking = TRUE;
10800     StartClocks();
10801
10802     if (appData.autoFlipView && flipView) {
10803       flipView = !flipView;
10804       DrawPosition(FALSE, NULL);
10805       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10806     }
10807     if(bookHit) { // [HGM] book: simulate book reply
10808         static char bookMove[MSG_SIZ]; // a bit generous?
10809
10810         programStats.nodes = programStats.depth = programStats.time = 
10811         programStats.score = programStats.got_only_move = 0;
10812         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10813
10814         strcpy(bookMove, "move ");
10815         strcat(bookMove, bookHit);
10816         HandleMachineMove(bookMove, &first);
10817     }
10818 }
10819
10820
10821 void
10822 DisplayTwoMachinesTitle()
10823 {
10824     char buf[MSG_SIZ];
10825     if (appData.matchGames > 0) {
10826         if (first.twoMachinesColor[0] == 'w') {
10827             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10828                     gameInfo.white, gameInfo.black,
10829                     first.matchWins, second.matchWins,
10830                     matchGame - 1 - (first.matchWins + second.matchWins));
10831         } else {
10832             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10833                     gameInfo.white, gameInfo.black,
10834                     second.matchWins, first.matchWins,
10835                     matchGame - 1 - (first.matchWins + second.matchWins));
10836         }
10837     } else {
10838         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10839     }
10840     DisplayTitle(buf);
10841 }
10842
10843 void
10844 TwoMachinesEvent P((void))
10845 {
10846     int i;
10847     char buf[MSG_SIZ];
10848     ChessProgramState *onmove;
10849     char *bookHit = NULL;
10850     
10851     if (appData.noChessProgram) return;
10852
10853     switch (gameMode) {
10854       case TwoMachinesPlay:
10855         return;
10856       case MachinePlaysWhite:
10857       case MachinePlaysBlack:
10858         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10859             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10860             return;
10861         }
10862         /* fall through */
10863       case BeginningOfGame:
10864       case PlayFromGameFile:
10865       case EndOfGame:
10866         EditGameEvent();
10867         if (gameMode != EditGame) return;
10868         break;
10869       case EditPosition:
10870         EditPositionDone();
10871         break;
10872       case AnalyzeMode:
10873       case AnalyzeFile:
10874         ExitAnalyzeMode();
10875         break;
10876       case EditGame:
10877       default:
10878         break;
10879     }
10880
10881     forwardMostMove = currentMove;
10882     ResurrectChessProgram();    /* in case first program isn't running */
10883
10884     if (second.pr == NULL) {
10885         StartChessProgram(&second);
10886         if (second.protocolVersion == 1) {
10887           TwoMachinesEventIfReady();
10888         } else {
10889           /* kludge: allow timeout for initial "feature" command */
10890           FreezeUI();
10891           DisplayMessage("", _("Starting second chess program"));
10892           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10893         }
10894         return;
10895     }
10896     DisplayMessage("", "");
10897     InitChessProgram(&second, FALSE);
10898     SendToProgram("force\n", &second);
10899     if (startedFromSetupPosition) {
10900         SendBoard(&second, backwardMostMove);
10901     if (appData.debugMode) {
10902         fprintf(debugFP, "Two Machines\n");
10903     }
10904     }
10905     for (i = backwardMostMove; i < forwardMostMove; i++) {
10906         SendMoveToProgram(i, &second);
10907     }
10908
10909     gameMode = TwoMachinesPlay;
10910     pausing = FALSE;
10911     ModeHighlight();
10912     SetGameInfo();
10913     DisplayTwoMachinesTitle();
10914     firstMove = TRUE;
10915     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10916         onmove = &first;
10917     } else {
10918         onmove = &second;
10919     }
10920
10921     SendToProgram(first.computerString, &first);
10922     if (first.sendName) {
10923       sprintf(buf, "name %s\n", second.tidy);
10924       SendToProgram(buf, &first);
10925     }
10926     SendToProgram(second.computerString, &second);
10927     if (second.sendName) {
10928       sprintf(buf, "name %s\n", first.tidy);
10929       SendToProgram(buf, &second);
10930     }
10931
10932     ResetClocks();
10933     if (!first.sendTime || !second.sendTime) {
10934         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10935         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10936     }
10937     if (onmove->sendTime) {
10938       if (onmove->useColors) {
10939         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10940       }
10941       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10942     }
10943     if (onmove->useColors) {
10944       SendToProgram(onmove->twoMachinesColor, onmove);
10945     }
10946     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10947 //    SendToProgram("go\n", onmove);
10948     onmove->maybeThinking = TRUE;
10949     SetMachineThinkingEnables();
10950
10951     StartClocks();
10952
10953     if(bookHit) { // [HGM] book: simulate book reply
10954         static char bookMove[MSG_SIZ]; // a bit generous?
10955
10956         programStats.nodes = programStats.depth = programStats.time = 
10957         programStats.score = programStats.got_only_move = 0;
10958         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10959
10960         strcpy(bookMove, "move ");
10961         strcat(bookMove, bookHit);
10962         savedMessage = bookMove; // args for deferred call
10963         savedState = onmove;
10964         ScheduleDelayedEvent(DeferredBookMove, 1);
10965     }
10966 }
10967
10968 void
10969 TrainingEvent()
10970 {
10971     if (gameMode == Training) {
10972       SetTrainingModeOff();
10973       gameMode = PlayFromGameFile;
10974       DisplayMessage("", _("Training mode off"));
10975     } else {
10976       gameMode = Training;
10977       animateTraining = appData.animate;
10978
10979       /* make sure we are not already at the end of the game */
10980       if (currentMove < forwardMostMove) {
10981         SetTrainingModeOn();
10982         DisplayMessage("", _("Training mode on"));
10983       } else {
10984         gameMode = PlayFromGameFile;
10985         DisplayError(_("Already at end of game"), 0);
10986       }
10987     }
10988     ModeHighlight();
10989 }
10990
10991 void
10992 IcsClientEvent()
10993 {
10994     if (!appData.icsActive) return;
10995     switch (gameMode) {
10996       case IcsPlayingWhite:
10997       case IcsPlayingBlack:
10998       case IcsObserving:
10999       case IcsIdle:
11000       case BeginningOfGame:
11001       case IcsExamining:
11002         return;
11003
11004       case EditGame:
11005         break;
11006
11007       case EditPosition:
11008         EditPositionDone();
11009         break;
11010
11011       case AnalyzeMode:
11012       case AnalyzeFile:
11013         ExitAnalyzeMode();
11014         break;
11015         
11016       default:
11017         EditGameEvent();
11018         break;
11019     }
11020
11021     gameMode = IcsIdle;
11022     ModeHighlight();
11023     return;
11024 }
11025
11026
11027 void
11028 EditGameEvent()
11029 {
11030     int i;
11031
11032     switch (gameMode) {
11033       case Training:
11034         SetTrainingModeOff();
11035         break;
11036       case MachinePlaysWhite:
11037       case MachinePlaysBlack:
11038       case BeginningOfGame:
11039         SendToProgram("force\n", &first);
11040         SetUserThinkingEnables();
11041         break;
11042       case PlayFromGameFile:
11043         (void) StopLoadGameTimer();
11044         if (gameFileFP != NULL) {
11045             gameFileFP = NULL;
11046         }
11047         break;
11048       case EditPosition:
11049         EditPositionDone();
11050         break;
11051       case AnalyzeMode:
11052       case AnalyzeFile:
11053         ExitAnalyzeMode();
11054         SendToProgram("force\n", &first);
11055         break;
11056       case TwoMachinesPlay:
11057         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11058         ResurrectChessProgram();
11059         SetUserThinkingEnables();
11060         break;
11061       case EndOfGame:
11062         ResurrectChessProgram();
11063         break;
11064       case IcsPlayingBlack:
11065       case IcsPlayingWhite:
11066         DisplayError(_("Warning: You are still playing a game"), 0);
11067         break;
11068       case IcsObserving:
11069         DisplayError(_("Warning: You are still observing a game"), 0);
11070         break;
11071       case IcsExamining:
11072         DisplayError(_("Warning: You are still examining a game"), 0);
11073         break;
11074       case IcsIdle:
11075         break;
11076       case EditGame:
11077       default:
11078         return;
11079     }
11080     
11081     pausing = FALSE;
11082     StopClocks();
11083     first.offeredDraw = second.offeredDraw = 0;
11084
11085     if (gameMode == PlayFromGameFile) {
11086         whiteTimeRemaining = timeRemaining[0][currentMove];
11087         blackTimeRemaining = timeRemaining[1][currentMove];
11088         DisplayTitle("");
11089     }
11090
11091     if (gameMode == MachinePlaysWhite ||
11092         gameMode == MachinePlaysBlack ||
11093         gameMode == TwoMachinesPlay ||
11094         gameMode == EndOfGame) {
11095         i = forwardMostMove;
11096         while (i > currentMove) {
11097             SendToProgram("undo\n", &first);
11098             i--;
11099         }
11100         whiteTimeRemaining = timeRemaining[0][currentMove];
11101         blackTimeRemaining = timeRemaining[1][currentMove];
11102         DisplayBothClocks();
11103         if (whiteFlag || blackFlag) {
11104             whiteFlag = blackFlag = 0;
11105         }
11106         DisplayTitle("");
11107     }           
11108     
11109     gameMode = EditGame;
11110     ModeHighlight();
11111     SetGameInfo();
11112 }
11113
11114
11115 void
11116 EditPositionEvent()
11117 {
11118     if (gameMode == EditPosition) {
11119         EditGameEvent();
11120         return;
11121     }
11122     
11123     EditGameEvent();
11124     if (gameMode != EditGame) return;
11125     
11126     gameMode = EditPosition;
11127     ModeHighlight();
11128     SetGameInfo();
11129     if (currentMove > 0)
11130       CopyBoard(boards[0], boards[currentMove]);
11131     
11132     blackPlaysFirst = !WhiteOnMove(currentMove);
11133     ResetClocks();
11134     currentMove = forwardMostMove = backwardMostMove = 0;
11135     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11136     DisplayMove(-1);
11137 }
11138
11139 void
11140 ExitAnalyzeMode()
11141 {
11142     /* [DM] icsEngineAnalyze - possible call from other functions */
11143     if (appData.icsEngineAnalyze) {
11144         appData.icsEngineAnalyze = FALSE;
11145
11146         DisplayMessage("",_("Close ICS engine analyze..."));
11147     }
11148     if (first.analysisSupport && first.analyzing) {
11149       SendToProgram("exit\n", &first);
11150       first.analyzing = FALSE;
11151     }
11152     thinkOutput[0] = NULLCHAR;
11153 }
11154
11155 void
11156 EditPositionDone()
11157 {
11158     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11159
11160     startedFromSetupPosition = TRUE;
11161     InitChessProgram(&first, FALSE);
11162     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11163     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11164         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11165         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11166     } else castlingRights[0][2] = -1;
11167     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11168         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11169         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11170     } else castlingRights[0][5] = -1;
11171     SendToProgram("force\n", &first);
11172     if (blackPlaysFirst) {
11173         strcpy(moveList[0], "");
11174         strcpy(parseList[0], "");
11175         currentMove = forwardMostMove = backwardMostMove = 1;
11176         CopyBoard(boards[1], boards[0]);
11177         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11178         { int i;
11179           epStatus[1] = epStatus[0];
11180           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11181         }
11182     } else {
11183         currentMove = forwardMostMove = backwardMostMove = 0;
11184     }
11185     SendBoard(&first, forwardMostMove);
11186     if (appData.debugMode) {
11187         fprintf(debugFP, "EditPosDone\n");
11188     }
11189     DisplayTitle("");
11190     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11191     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11192     gameMode = EditGame;
11193     ModeHighlight();
11194     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11195     ClearHighlights(); /* [AS] */
11196 }
11197
11198 /* Pause for `ms' milliseconds */
11199 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11200 void
11201 TimeDelay(ms)
11202      long ms;
11203 {
11204     TimeMark m1, m2;
11205
11206     GetTimeMark(&m1);
11207     do {
11208         GetTimeMark(&m2);
11209     } while (SubtractTimeMarks(&m2, &m1) < ms);
11210 }
11211
11212 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11213 void
11214 SendMultiLineToICS(buf)
11215      char *buf;
11216 {
11217     char temp[MSG_SIZ+1], *p;
11218     int len;
11219
11220     len = strlen(buf);
11221     if (len > MSG_SIZ)
11222       len = MSG_SIZ;
11223   
11224     strncpy(temp, buf, len);
11225     temp[len] = 0;
11226
11227     p = temp;
11228     while (*p) {
11229         if (*p == '\n' || *p == '\r')
11230           *p = ' ';
11231         ++p;
11232     }
11233
11234     strcat(temp, "\n");
11235     SendToICS(temp);
11236     SendToPlayer(temp, strlen(temp));
11237 }
11238
11239 void
11240 SetWhiteToPlayEvent()
11241 {
11242     if (gameMode == EditPosition) {
11243         blackPlaysFirst = FALSE;
11244         DisplayBothClocks();    /* works because currentMove is 0 */
11245     } else if (gameMode == IcsExamining) {
11246         SendToICS(ics_prefix);
11247         SendToICS("tomove white\n");
11248     }
11249 }
11250
11251 void
11252 SetBlackToPlayEvent()
11253 {
11254     if (gameMode == EditPosition) {
11255         blackPlaysFirst = TRUE;
11256         currentMove = 1;        /* kludge */
11257         DisplayBothClocks();
11258         currentMove = 0;
11259     } else if (gameMode == IcsExamining) {
11260         SendToICS(ics_prefix);
11261         SendToICS("tomove black\n");
11262     }
11263 }
11264
11265 void
11266 EditPositionMenuEvent(selection, x, y)
11267      ChessSquare selection;
11268      int x, y;
11269 {
11270     char buf[MSG_SIZ];
11271     ChessSquare piece = boards[0][y][x];
11272
11273     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11274
11275     switch (selection) {
11276       case ClearBoard:
11277         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11278             SendToICS(ics_prefix);
11279             SendToICS("bsetup clear\n");
11280         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11281             SendToICS(ics_prefix);
11282             SendToICS("clearboard\n");
11283         } else {
11284             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11285                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11286                 for (y = 0; y < BOARD_HEIGHT; y++) {
11287                     if (gameMode == IcsExamining) {
11288                         if (boards[currentMove][y][x] != EmptySquare) {
11289                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11290                                     AAA + x, ONE + y);
11291                             SendToICS(buf);
11292                         }
11293                     } else {
11294                         boards[0][y][x] = p;
11295                     }
11296                 }
11297             }
11298         }
11299         if (gameMode == EditPosition) {
11300             DrawPosition(FALSE, boards[0]);
11301         }
11302         break;
11303
11304       case WhitePlay:
11305         SetWhiteToPlayEvent();
11306         break;
11307
11308       case BlackPlay:
11309         SetBlackToPlayEvent();
11310         break;
11311
11312       case EmptySquare:
11313         if (gameMode == IcsExamining) {
11314             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11315             SendToICS(buf);
11316         } else {
11317             boards[0][y][x] = EmptySquare;
11318             DrawPosition(FALSE, boards[0]);
11319         }
11320         break;
11321
11322       case PromotePiece:
11323         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11324            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11325             selection = (ChessSquare) (PROMOTED piece);
11326         } else if(piece == EmptySquare) selection = WhiteSilver;
11327         else selection = (ChessSquare)((int)piece - 1);
11328         goto defaultlabel;
11329
11330       case DemotePiece:
11331         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11332            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11333             selection = (ChessSquare) (DEMOTED piece);
11334         } else if(piece == EmptySquare) selection = BlackSilver;
11335         else selection = (ChessSquare)((int)piece + 1);       
11336         goto defaultlabel;
11337
11338       case WhiteQueen:
11339       case BlackQueen:
11340         if(gameInfo.variant == VariantShatranj ||
11341            gameInfo.variant == VariantXiangqi  ||
11342            gameInfo.variant == VariantCourier    )
11343             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11344         goto defaultlabel;
11345
11346       case WhiteKing:
11347       case BlackKing:
11348         if(gameInfo.variant == VariantXiangqi)
11349             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11350         if(gameInfo.variant == VariantKnightmate)
11351             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11352       default:
11353         defaultlabel:
11354         if (gameMode == IcsExamining) {
11355             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11356                     PieceToChar(selection), AAA + x, ONE + y);
11357             SendToICS(buf);
11358         } else {
11359             boards[0][y][x] = selection;
11360             DrawPosition(FALSE, boards[0]);
11361         }
11362         break;
11363     }
11364 }
11365
11366
11367 void
11368 DropMenuEvent(selection, x, y)
11369      ChessSquare selection;
11370      int x, y;
11371 {
11372     ChessMove moveType;
11373
11374     switch (gameMode) {
11375       case IcsPlayingWhite:
11376       case MachinePlaysBlack:
11377         if (!WhiteOnMove(currentMove)) {
11378             DisplayMoveError(_("It is Black's turn"));
11379             return;
11380         }
11381         moveType = WhiteDrop;
11382         break;
11383       case IcsPlayingBlack:
11384       case MachinePlaysWhite:
11385         if (WhiteOnMove(currentMove)) {
11386             DisplayMoveError(_("It is White's turn"));
11387             return;
11388         }
11389         moveType = BlackDrop;
11390         break;
11391       case EditGame:
11392         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11393         break;
11394       default:
11395         return;
11396     }
11397
11398     if (moveType == BlackDrop && selection < BlackPawn) {
11399       selection = (ChessSquare) ((int) selection
11400                                  + (int) BlackPawn - (int) WhitePawn);
11401     }
11402     if (boards[currentMove][y][x] != EmptySquare) {
11403         DisplayMoveError(_("That square is occupied"));
11404         return;
11405     }
11406
11407     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11408 }
11409
11410 void
11411 AcceptEvent()
11412 {
11413     /* Accept a pending offer of any kind from opponent */
11414     
11415     if (appData.icsActive) {
11416         SendToICS(ics_prefix);
11417         SendToICS("accept\n");
11418     } else if (cmailMsgLoaded) {
11419         if (currentMove == cmailOldMove &&
11420             commentList[cmailOldMove] != NULL &&
11421             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11422                    "Black offers a draw" : "White offers a draw")) {
11423             TruncateGame();
11424             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11425             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11426         } else {
11427             DisplayError(_("There is no pending offer on this move"), 0);
11428             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11429         }
11430     } else {
11431         /* Not used for offers from chess program */
11432     }
11433 }
11434
11435 void
11436 DeclineEvent()
11437 {
11438     /* Decline a pending offer of any kind from opponent */
11439     
11440     if (appData.icsActive) {
11441         SendToICS(ics_prefix);
11442         SendToICS("decline\n");
11443     } else if (cmailMsgLoaded) {
11444         if (currentMove == cmailOldMove &&
11445             commentList[cmailOldMove] != NULL &&
11446             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11447                    "Black offers a draw" : "White offers a draw")) {
11448 #ifdef NOTDEF
11449             AppendComment(cmailOldMove, "Draw declined");
11450             DisplayComment(cmailOldMove - 1, "Draw declined");
11451 #endif /*NOTDEF*/
11452         } else {
11453             DisplayError(_("There is no pending offer on this move"), 0);
11454         }
11455     } else {
11456         /* Not used for offers from chess program */
11457     }
11458 }
11459
11460 void
11461 RematchEvent()
11462 {
11463     /* Issue ICS rematch command */
11464     if (appData.icsActive) {
11465         SendToICS(ics_prefix);
11466         SendToICS("rematch\n");
11467     }
11468 }
11469
11470 void
11471 CallFlagEvent()
11472 {
11473     /* Call your opponent's flag (claim a win on time) */
11474     if (appData.icsActive) {
11475         SendToICS(ics_prefix);
11476         SendToICS("flag\n");
11477     } else {
11478         switch (gameMode) {
11479           default:
11480             return;
11481           case MachinePlaysWhite:
11482             if (whiteFlag) {
11483                 if (blackFlag)
11484                   GameEnds(GameIsDrawn, "Both players ran out of time",
11485                            GE_PLAYER);
11486                 else
11487                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11488             } else {
11489                 DisplayError(_("Your opponent is not out of time"), 0);
11490             }
11491             break;
11492           case MachinePlaysBlack:
11493             if (blackFlag) {
11494                 if (whiteFlag)
11495                   GameEnds(GameIsDrawn, "Both players ran out of time",
11496                            GE_PLAYER);
11497                 else
11498                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11499             } else {
11500                 DisplayError(_("Your opponent is not out of time"), 0);
11501             }
11502             break;
11503         }
11504     }
11505 }
11506
11507 void
11508 DrawEvent()
11509 {
11510     /* Offer draw or accept pending draw offer from opponent */
11511     
11512     if (appData.icsActive) {
11513         /* Note: tournament rules require draw offers to be
11514            made after you make your move but before you punch
11515            your clock.  Currently ICS doesn't let you do that;
11516            instead, you immediately punch your clock after making
11517            a move, but you can offer a draw at any time. */
11518         
11519         SendToICS(ics_prefix);
11520         SendToICS("draw\n");
11521     } else if (cmailMsgLoaded) {
11522         if (currentMove == cmailOldMove &&
11523             commentList[cmailOldMove] != NULL &&
11524             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11525                    "Black offers a draw" : "White offers a draw")) {
11526             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11527             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11528         } else if (currentMove == cmailOldMove + 1) {
11529             char *offer = WhiteOnMove(cmailOldMove) ?
11530               "White offers a draw" : "Black offers a draw";
11531             AppendComment(currentMove, offer);
11532             DisplayComment(currentMove - 1, offer);
11533             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11534         } else {
11535             DisplayError(_("You must make your move before offering a draw"), 0);
11536             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11537         }
11538     } else if (first.offeredDraw) {
11539         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11540     } else {
11541         if (first.sendDrawOffers) {
11542             SendToProgram("draw\n", &first);
11543             userOfferedDraw = TRUE;
11544         }
11545     }
11546 }
11547
11548 void
11549 AdjournEvent()
11550 {
11551     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11552     
11553     if (appData.icsActive) {
11554         SendToICS(ics_prefix);
11555         SendToICS("adjourn\n");
11556     } else {
11557         /* Currently GNU Chess doesn't offer or accept Adjourns */
11558     }
11559 }
11560
11561
11562 void
11563 AbortEvent()
11564 {
11565     /* Offer Abort or accept pending Abort offer from opponent */
11566     
11567     if (appData.icsActive) {
11568         SendToICS(ics_prefix);
11569         SendToICS("abort\n");
11570     } else {
11571         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11572     }
11573 }
11574
11575 void
11576 ResignEvent()
11577 {
11578     /* Resign.  You can do this even if it's not your turn. */
11579     
11580     if (appData.icsActive) {
11581         SendToICS(ics_prefix);
11582         SendToICS("resign\n");
11583     } else {
11584         switch (gameMode) {
11585           case MachinePlaysWhite:
11586             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11587             break;
11588           case MachinePlaysBlack:
11589             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11590             break;
11591           case EditGame:
11592             if (cmailMsgLoaded) {
11593                 TruncateGame();
11594                 if (WhiteOnMove(cmailOldMove)) {
11595                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11596                 } else {
11597                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11598                 }
11599                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11600             }
11601             break;
11602           default:
11603             break;
11604         }
11605     }
11606 }
11607
11608
11609 void
11610 StopObservingEvent()
11611 {
11612     /* Stop observing current games */
11613     SendToICS(ics_prefix);
11614     SendToICS("unobserve\n");
11615 }
11616
11617 void
11618 StopExaminingEvent()
11619 {
11620     /* Stop observing current game */
11621     SendToICS(ics_prefix);
11622     SendToICS("unexamine\n");
11623 }
11624
11625 void
11626 ForwardInner(target)
11627      int target;
11628 {
11629     int limit;
11630
11631     if (appData.debugMode)
11632         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11633                 target, currentMove, forwardMostMove);
11634
11635     if (gameMode == EditPosition)
11636       return;
11637
11638     if (gameMode == PlayFromGameFile && !pausing)
11639       PauseEvent();
11640     
11641     if (gameMode == IcsExamining && pausing)
11642       limit = pauseExamForwardMostMove;
11643     else
11644       limit = forwardMostMove;
11645     
11646     if (target > limit) target = limit;
11647
11648     if (target > 0 && moveList[target - 1][0]) {
11649         int fromX, fromY, toX, toY;
11650         toX = moveList[target - 1][2] - AAA;
11651         toY = moveList[target - 1][3] - ONE;
11652         if (moveList[target - 1][1] == '@') {
11653             if (appData.highlightLastMove) {
11654                 SetHighlights(-1, -1, toX, toY);
11655             }
11656         } else {
11657             fromX = moveList[target - 1][0] - AAA;
11658             fromY = moveList[target - 1][1] - ONE;
11659             if (target == currentMove + 1) {
11660                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11661             }
11662             if (appData.highlightLastMove) {
11663                 SetHighlights(fromX, fromY, toX, toY);
11664             }
11665         }
11666     }
11667     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11668         gameMode == Training || gameMode == PlayFromGameFile || 
11669         gameMode == AnalyzeFile) {
11670         while (currentMove < target) {
11671             SendMoveToProgram(currentMove++, &first);
11672         }
11673     } else {
11674         currentMove = target;
11675     }
11676     
11677     if (gameMode == EditGame || gameMode == EndOfGame) {
11678         whiteTimeRemaining = timeRemaining[0][currentMove];
11679         blackTimeRemaining = timeRemaining[1][currentMove];
11680     }
11681     DisplayBothClocks();
11682     DisplayMove(currentMove - 1);
11683     DrawPosition(FALSE, boards[currentMove]);
11684     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11685     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11686         DisplayComment(currentMove - 1, commentList[currentMove]);
11687     }
11688 }
11689
11690
11691 void
11692 ForwardEvent()
11693 {
11694     if (gameMode == IcsExamining && !pausing) {
11695         SendToICS(ics_prefix);
11696         SendToICS("forward\n");
11697     } else {
11698         ForwardInner(currentMove + 1);
11699     }
11700 }
11701
11702 void
11703 ToEndEvent()
11704 {
11705     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11706         /* to optimze, we temporarily turn off analysis mode while we feed
11707          * the remaining moves to the engine. Otherwise we get analysis output
11708          * after each move.
11709          */ 
11710         if (first.analysisSupport) {
11711           SendToProgram("exit\nforce\n", &first);
11712           first.analyzing = FALSE;
11713         }
11714     }
11715         
11716     if (gameMode == IcsExamining && !pausing) {
11717         SendToICS(ics_prefix);
11718         SendToICS("forward 999999\n");
11719     } else {
11720         ForwardInner(forwardMostMove);
11721     }
11722
11723     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11724         /* we have fed all the moves, so reactivate analysis mode */
11725         SendToProgram("analyze\n", &first);
11726         first.analyzing = TRUE;
11727         /*first.maybeThinking = TRUE;*/
11728         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11729     }
11730 }
11731
11732 void
11733 BackwardInner(target)
11734      int target;
11735 {
11736     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11737
11738     if (appData.debugMode)
11739         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11740                 target, currentMove, forwardMostMove);
11741
11742     if (gameMode == EditPosition) return;
11743     if (currentMove <= backwardMostMove) {
11744         ClearHighlights();
11745         DrawPosition(full_redraw, boards[currentMove]);
11746         return;
11747     }
11748     if (gameMode == PlayFromGameFile && !pausing)
11749       PauseEvent();
11750     
11751     if (moveList[target][0]) {
11752         int fromX, fromY, toX, toY;
11753         toX = moveList[target][2] - AAA;
11754         toY = moveList[target][3] - ONE;
11755         if (moveList[target][1] == '@') {
11756             if (appData.highlightLastMove) {
11757                 SetHighlights(-1, -1, toX, toY);
11758             }
11759         } else {
11760             fromX = moveList[target][0] - AAA;
11761             fromY = moveList[target][1] - ONE;
11762             if (target == currentMove - 1) {
11763                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11764             }
11765             if (appData.highlightLastMove) {
11766                 SetHighlights(fromX, fromY, toX, toY);
11767             }
11768         }
11769     }
11770     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11771         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11772         while (currentMove > target) {
11773             SendToProgram("undo\n", &first);
11774             currentMove--;
11775         }
11776     } else {
11777         currentMove = target;
11778     }
11779     
11780     if (gameMode == EditGame || gameMode == EndOfGame) {
11781         whiteTimeRemaining = timeRemaining[0][currentMove];
11782         blackTimeRemaining = timeRemaining[1][currentMove];
11783     }
11784     DisplayBothClocks();
11785     DisplayMove(currentMove - 1);
11786     DrawPosition(full_redraw, boards[currentMove]);
11787     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11788     // [HGM] PV info: routine tests if comment empty
11789     DisplayComment(currentMove - 1, commentList[currentMove]);
11790 }
11791
11792 void
11793 BackwardEvent()
11794 {
11795     if (gameMode == IcsExamining && !pausing) {
11796         SendToICS(ics_prefix);
11797         SendToICS("backward\n");
11798     } else {
11799         BackwardInner(currentMove - 1);
11800     }
11801 }
11802
11803 void
11804 ToStartEvent()
11805 {
11806     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11807         /* to optimze, we temporarily turn off analysis mode while we undo
11808          * all the moves. Otherwise we get analysis output after each undo.
11809          */ 
11810         if (first.analysisSupport) {
11811           SendToProgram("exit\nforce\n", &first);
11812           first.analyzing = FALSE;
11813         }
11814     }
11815
11816     if (gameMode == IcsExamining && !pausing) {
11817         SendToICS(ics_prefix);
11818         SendToICS("backward 999999\n");
11819     } else {
11820         BackwardInner(backwardMostMove);
11821     }
11822
11823     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11824         /* we have fed all the moves, so reactivate analysis mode */
11825         SendToProgram("analyze\n", &first);
11826         first.analyzing = TRUE;
11827         /*first.maybeThinking = TRUE;*/
11828         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11829     }
11830 }
11831
11832 void
11833 ToNrEvent(int to)
11834 {
11835   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11836   if (to >= forwardMostMove) to = forwardMostMove;
11837   if (to <= backwardMostMove) to = backwardMostMove;
11838   if (to < currentMove) {
11839     BackwardInner(to);
11840   } else {
11841     ForwardInner(to);
11842   }
11843 }
11844
11845 void
11846 RevertEvent()
11847 {
11848     if (gameMode != IcsExamining) {
11849         DisplayError(_("You are not examining a game"), 0);
11850         return;
11851     }
11852     if (pausing) {
11853         DisplayError(_("You can't revert while pausing"), 0);
11854         return;
11855     }
11856     SendToICS(ics_prefix);
11857     SendToICS("revert\n");
11858 }
11859
11860 void
11861 RetractMoveEvent()
11862 {
11863     switch (gameMode) {
11864       case MachinePlaysWhite:
11865       case MachinePlaysBlack:
11866         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11867             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11868             return;
11869         }
11870         if (forwardMostMove < 2) return;
11871         currentMove = forwardMostMove = forwardMostMove - 2;
11872         whiteTimeRemaining = timeRemaining[0][currentMove];
11873         blackTimeRemaining = timeRemaining[1][currentMove];
11874         DisplayBothClocks();
11875         DisplayMove(currentMove - 1);
11876         ClearHighlights();/*!! could figure this out*/
11877         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11878         SendToProgram("remove\n", &first);
11879         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11880         break;
11881
11882       case BeginningOfGame:
11883       default:
11884         break;
11885
11886       case IcsPlayingWhite:
11887       case IcsPlayingBlack:
11888         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11889             SendToICS(ics_prefix);
11890             SendToICS("takeback 2\n");
11891         } else {
11892             SendToICS(ics_prefix);
11893             SendToICS("takeback 1\n");
11894         }
11895         break;
11896     }
11897 }
11898
11899 void
11900 MoveNowEvent()
11901 {
11902     ChessProgramState *cps;
11903
11904     switch (gameMode) {
11905       case MachinePlaysWhite:
11906         if (!WhiteOnMove(forwardMostMove)) {
11907             DisplayError(_("It is your turn"), 0);
11908             return;
11909         }
11910         cps = &first;
11911         break;
11912       case MachinePlaysBlack:
11913         if (WhiteOnMove(forwardMostMove)) {
11914             DisplayError(_("It is your turn"), 0);
11915             return;
11916         }
11917         cps = &first;
11918         break;
11919       case TwoMachinesPlay:
11920         if (WhiteOnMove(forwardMostMove) ==
11921             (first.twoMachinesColor[0] == 'w')) {
11922             cps = &first;
11923         } else {
11924             cps = &second;
11925         }
11926         break;
11927       case BeginningOfGame:
11928       default:
11929         return;
11930     }
11931     SendToProgram("?\n", cps);
11932 }
11933
11934 void
11935 TruncateGameEvent()
11936 {
11937     EditGameEvent();
11938     if (gameMode != EditGame) return;
11939     TruncateGame();
11940 }
11941
11942 void
11943 TruncateGame()
11944 {
11945     if (forwardMostMove > currentMove) {
11946         if (gameInfo.resultDetails != NULL) {
11947             free(gameInfo.resultDetails);
11948             gameInfo.resultDetails = NULL;
11949             gameInfo.result = GameUnfinished;
11950         }
11951         forwardMostMove = currentMove;
11952         HistorySet(parseList, backwardMostMove, forwardMostMove,
11953                    currentMove-1);
11954     }
11955 }
11956
11957 void
11958 HintEvent()
11959 {
11960     if (appData.noChessProgram) return;
11961     switch (gameMode) {
11962       case MachinePlaysWhite:
11963         if (WhiteOnMove(forwardMostMove)) {
11964             DisplayError(_("Wait until your turn"), 0);
11965             return;
11966         }
11967         break;
11968       case BeginningOfGame:
11969       case MachinePlaysBlack:
11970         if (!WhiteOnMove(forwardMostMove)) {
11971             DisplayError(_("Wait until your turn"), 0);
11972             return;
11973         }
11974         break;
11975       default:
11976         DisplayError(_("No hint available"), 0);
11977         return;
11978     }
11979     SendToProgram("hint\n", &first);
11980     hintRequested = TRUE;
11981 }
11982
11983 void
11984 BookEvent()
11985 {
11986     if (appData.noChessProgram) return;
11987     switch (gameMode) {
11988       case MachinePlaysWhite:
11989         if (WhiteOnMove(forwardMostMove)) {
11990             DisplayError(_("Wait until your turn"), 0);
11991             return;
11992         }
11993         break;
11994       case BeginningOfGame:
11995       case MachinePlaysBlack:
11996         if (!WhiteOnMove(forwardMostMove)) {
11997             DisplayError(_("Wait until your turn"), 0);
11998             return;
11999         }
12000         break;
12001       case EditPosition:
12002         EditPositionDone();
12003         break;
12004       case TwoMachinesPlay:
12005         return;
12006       default:
12007         break;
12008     }
12009     SendToProgram("bk\n", &first);
12010     bookOutput[0] = NULLCHAR;
12011     bookRequested = TRUE;
12012 }
12013
12014 void
12015 AboutGameEvent()
12016 {
12017     char *tags = PGNTags(&gameInfo);
12018     TagsPopUp(tags, CmailMsg());
12019     free(tags);
12020 }
12021
12022 /* end button procedures */
12023
12024 void
12025 PrintPosition(fp, move)
12026      FILE *fp;
12027      int move;
12028 {
12029     int i, j;
12030     
12031     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12032         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12033             char c = PieceToChar(boards[move][i][j]);
12034             fputc(c == 'x' ? '.' : c, fp);
12035             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12036         }
12037     }
12038     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12039       fprintf(fp, "white to play\n");
12040     else
12041       fprintf(fp, "black to play\n");
12042 }
12043
12044 void
12045 PrintOpponents(fp)
12046      FILE *fp;
12047 {
12048     if (gameInfo.white != NULL) {
12049         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12050     } else {
12051         fprintf(fp, "\n");
12052     }
12053 }
12054
12055 /* Find last component of program's own name, using some heuristics */
12056 void
12057 TidyProgramName(prog, host, buf)
12058      char *prog, *host, buf[MSG_SIZ];
12059 {
12060     char *p, *q;
12061     int local = (strcmp(host, "localhost") == 0);
12062     while (!local && (p = strchr(prog, ';')) != NULL) {
12063         p++;
12064         while (*p == ' ') p++;
12065         prog = p;
12066     }
12067     if (*prog == '"' || *prog == '\'') {
12068         q = strchr(prog + 1, *prog);
12069     } else {
12070         q = strchr(prog, ' ');
12071     }
12072     if (q == NULL) q = prog + strlen(prog);
12073     p = q;
12074     while (p >= prog && *p != '/' && *p != '\\') p--;
12075     p++;
12076     if(p == prog && *p == '"') p++;
12077     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12078     memcpy(buf, p, q - p);
12079     buf[q - p] = NULLCHAR;
12080     if (!local) {
12081         strcat(buf, "@");
12082         strcat(buf, host);
12083     }
12084 }
12085
12086 char *
12087 TimeControlTagValue()
12088 {
12089     char buf[MSG_SIZ];
12090     if (!appData.clockMode) {
12091         strcpy(buf, "-");
12092     } else if (movesPerSession > 0) {
12093         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12094     } else if (timeIncrement == 0) {
12095         sprintf(buf, "%ld", timeControl/1000);
12096     } else {
12097         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12098     }
12099     return StrSave(buf);
12100 }
12101
12102 void
12103 SetGameInfo()
12104 {
12105     /* This routine is used only for certain modes */
12106     VariantClass v = gameInfo.variant;
12107     ClearGameInfo(&gameInfo);
12108     gameInfo.variant = v;
12109
12110     switch (gameMode) {
12111       case MachinePlaysWhite:
12112         gameInfo.event = StrSave( appData.pgnEventHeader );
12113         gameInfo.site = StrSave(HostName());
12114         gameInfo.date = PGNDate();
12115         gameInfo.round = StrSave("-");
12116         gameInfo.white = StrSave(first.tidy);
12117         gameInfo.black = StrSave(UserName());
12118         gameInfo.timeControl = TimeControlTagValue();
12119         break;
12120
12121       case MachinePlaysBlack:
12122         gameInfo.event = StrSave( appData.pgnEventHeader );
12123         gameInfo.site = StrSave(HostName());
12124         gameInfo.date = PGNDate();
12125         gameInfo.round = StrSave("-");
12126         gameInfo.white = StrSave(UserName());
12127         gameInfo.black = StrSave(first.tidy);
12128         gameInfo.timeControl = TimeControlTagValue();
12129         break;
12130
12131       case TwoMachinesPlay:
12132         gameInfo.event = StrSave( appData.pgnEventHeader );
12133         gameInfo.site = StrSave(HostName());
12134         gameInfo.date = PGNDate();
12135         if (matchGame > 0) {
12136             char buf[MSG_SIZ];
12137             sprintf(buf, "%d", matchGame);
12138             gameInfo.round = StrSave(buf);
12139         } else {
12140             gameInfo.round = StrSave("-");
12141         }
12142         if (first.twoMachinesColor[0] == 'w') {
12143             gameInfo.white = StrSave(first.tidy);
12144             gameInfo.black = StrSave(second.tidy);
12145         } else {
12146             gameInfo.white = StrSave(second.tidy);
12147             gameInfo.black = StrSave(first.tidy);
12148         }
12149         gameInfo.timeControl = TimeControlTagValue();
12150         break;
12151
12152       case EditGame:
12153         gameInfo.event = StrSave("Edited game");
12154         gameInfo.site = StrSave(HostName());
12155         gameInfo.date = PGNDate();
12156         gameInfo.round = StrSave("-");
12157         gameInfo.white = StrSave("-");
12158         gameInfo.black = StrSave("-");
12159         break;
12160
12161       case EditPosition:
12162         gameInfo.event = StrSave("Edited position");
12163         gameInfo.site = StrSave(HostName());
12164         gameInfo.date = PGNDate();
12165         gameInfo.round = StrSave("-");
12166         gameInfo.white = StrSave("-");
12167         gameInfo.black = StrSave("-");
12168         break;
12169
12170       case IcsPlayingWhite:
12171       case IcsPlayingBlack:
12172       case IcsObserving:
12173       case IcsExamining:
12174         break;
12175
12176       case PlayFromGameFile:
12177         gameInfo.event = StrSave("Game from non-PGN file");
12178         gameInfo.site = StrSave(HostName());
12179         gameInfo.date = PGNDate();
12180         gameInfo.round = StrSave("-");
12181         gameInfo.white = StrSave("?");
12182         gameInfo.black = StrSave("?");
12183         break;
12184
12185       default:
12186         break;
12187     }
12188 }
12189
12190 void
12191 ReplaceComment(index, text)
12192      int index;
12193      char *text;
12194 {
12195     int len;
12196
12197     while (*text == '\n') text++;
12198     len = strlen(text);
12199     while (len > 0 && text[len - 1] == '\n') len--;
12200
12201     if (commentList[index] != NULL)
12202       free(commentList[index]);
12203
12204     if (len == 0) {
12205         commentList[index] = NULL;
12206         return;
12207     }
12208     commentList[index] = (char *) malloc(len + 2);
12209     strncpy(commentList[index], text, len);
12210     commentList[index][len] = '\n';
12211     commentList[index][len + 1] = NULLCHAR;
12212 }
12213
12214 void
12215 CrushCRs(text)
12216      char *text;
12217 {
12218   char *p = text;
12219   char *q = text;
12220   char ch;
12221
12222   do {
12223     ch = *p++;
12224     if (ch == '\r') continue;
12225     *q++ = ch;
12226   } while (ch != '\0');
12227 }
12228
12229 void
12230 AppendComment(index, text)
12231      int index;
12232      char *text;
12233 {
12234     int oldlen, len;
12235     char *old;
12236
12237     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12238
12239     CrushCRs(text);
12240     while (*text == '\n') text++;
12241     len = strlen(text);
12242     while (len > 0 && text[len - 1] == '\n') len--;
12243
12244     if (len == 0) return;
12245
12246     if (commentList[index] != NULL) {
12247         old = commentList[index];
12248         oldlen = strlen(old);
12249         commentList[index] = (char *) malloc(oldlen + len + 2);
12250         strcpy(commentList[index], old);
12251         free(old);
12252         strncpy(&commentList[index][oldlen], text, len);
12253         commentList[index][oldlen + len] = '\n';
12254         commentList[index][oldlen + len + 1] = NULLCHAR;
12255     } else {
12256         commentList[index] = (char *) malloc(len + 2);
12257         strncpy(commentList[index], text, len);
12258         commentList[index][len] = '\n';
12259         commentList[index][len + 1] = NULLCHAR;
12260     }
12261 }
12262
12263 static char * FindStr( char * text, char * sub_text )
12264 {
12265     char * result = strstr( text, sub_text );
12266
12267     if( result != NULL ) {
12268         result += strlen( sub_text );
12269     }
12270
12271     return result;
12272 }
12273
12274 /* [AS] Try to extract PV info from PGN comment */
12275 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12276 char *GetInfoFromComment( int index, char * text )
12277 {
12278     char * sep = text;
12279
12280     if( text != NULL && index > 0 ) {
12281         int score = 0;
12282         int depth = 0;
12283         int time = -1, sec = 0, deci;
12284         char * s_eval = FindStr( text, "[%eval " );
12285         char * s_emt = FindStr( text, "[%emt " );
12286
12287         if( s_eval != NULL || s_emt != NULL ) {
12288             /* New style */
12289             char delim;
12290
12291             if( s_eval != NULL ) {
12292                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12293                     return text;
12294                 }
12295
12296                 if( delim != ']' ) {
12297                     return text;
12298                 }
12299             }
12300
12301             if( s_emt != NULL ) {
12302             }
12303         }
12304         else {
12305             /* We expect something like: [+|-]nnn.nn/dd */
12306             int score_lo = 0;
12307
12308             sep = strchr( text, '/' );
12309             if( sep == NULL || sep < (text+4) ) {
12310                 return text;
12311             }
12312
12313             time = -1; sec = -1; deci = -1;
12314             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12315                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12316                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12317                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12318                 return text;
12319             }
12320
12321             if( score_lo < 0 || score_lo >= 100 ) {
12322                 return text;
12323             }
12324
12325             if(sec >= 0) time = 600*time + 10*sec; else
12326             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12327
12328             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12329
12330             /* [HGM] PV time: now locate end of PV info */
12331             while( *++sep >= '0' && *sep <= '9'); // strip depth
12332             if(time >= 0)
12333             while( *++sep >= '0' && *sep <= '9'); // strip time
12334             if(sec >= 0)
12335             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12336             if(deci >= 0)
12337             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12338             while(*sep == ' ') sep++;
12339         }
12340
12341         if( depth <= 0 ) {
12342             return text;
12343         }
12344
12345         if( time < 0 ) {
12346             time = -1;
12347         }
12348
12349         pvInfoList[index-1].depth = depth;
12350         pvInfoList[index-1].score = score;
12351         pvInfoList[index-1].time  = 10*time; // centi-sec
12352     }
12353     return sep;
12354 }
12355
12356 void
12357 SendToProgram(message, cps)
12358      char *message;
12359      ChessProgramState *cps;
12360 {
12361     int count, outCount, error;
12362     char buf[MSG_SIZ];
12363
12364     if (cps->pr == NULL) return;
12365     Attention(cps);
12366     
12367     if (appData.debugMode) {
12368         TimeMark now;
12369         GetTimeMark(&now);
12370         fprintf(debugFP, "%ld >%-6s: %s", 
12371                 SubtractTimeMarks(&now, &programStartTime),
12372                 cps->which, message);
12373     }
12374     
12375     count = strlen(message);
12376     outCount = OutputToProcess(cps->pr, message, count, &error);
12377     if (outCount < count && !exiting 
12378                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12379         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12380         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12381             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12382                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12383                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12384             } else {
12385                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12386             }
12387             gameInfo.resultDetails = buf;
12388         }
12389         DisplayFatalError(buf, error, 1);
12390     }
12391 }
12392
12393 void
12394 ReceiveFromProgram(isr, closure, message, count, error)
12395      InputSourceRef isr;
12396      VOIDSTAR closure;
12397      char *message;
12398      int count;
12399      int error;
12400 {
12401     char *end_str;
12402     char buf[MSG_SIZ];
12403     ChessProgramState *cps = (ChessProgramState *)closure;
12404
12405     if (isr != cps->isr) return; /* Killed intentionally */
12406     if (count <= 0) {
12407         if (count == 0) {
12408             sprintf(buf,
12409                     _("Error: %s chess program (%s) exited unexpectedly"),
12410                     cps->which, cps->program);
12411         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12412                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12413                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12414                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12415                 } else {
12416                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12417                 }
12418                 gameInfo.resultDetails = buf;
12419             }
12420             RemoveInputSource(cps->isr);
12421             DisplayFatalError(buf, 0, 1);
12422         } else {
12423             sprintf(buf,
12424                     _("Error reading from %s chess program (%s)"),
12425                     cps->which, cps->program);
12426             RemoveInputSource(cps->isr);
12427
12428             /* [AS] Program is misbehaving badly... kill it */
12429             if( count == -2 ) {
12430                 DestroyChildProcess( cps->pr, 9 );
12431                 cps->pr = NoProc;
12432             }
12433
12434             DisplayFatalError(buf, error, 1);
12435         }
12436         return;
12437     }
12438     
12439     if ((end_str = strchr(message, '\r')) != NULL)
12440       *end_str = NULLCHAR;
12441     if ((end_str = strchr(message, '\n')) != NULL)
12442       *end_str = NULLCHAR;
12443     
12444     if (appData.debugMode) {
12445         TimeMark now; int print = 1;
12446         char *quote = ""; char c; int i;
12447
12448         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12449                 char start = message[0];
12450                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12451                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12452                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12453                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12454                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12455                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12456                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12457                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12458                         { quote = "# "; print = (appData.engineComments == 2); }
12459                 message[0] = start; // restore original message
12460         }
12461         if(print) {
12462                 GetTimeMark(&now);
12463                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12464                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12465                         quote,
12466                         message);
12467         }
12468     }
12469
12470     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12471     if (appData.icsEngineAnalyze) {
12472         if (strstr(message, "whisper") != NULL ||
12473              strstr(message, "kibitz") != NULL || 
12474             strstr(message, "tellics") != NULL) return;
12475     }
12476
12477     HandleMachineMove(message, cps);
12478 }
12479
12480
12481 void
12482 SendTimeControl(cps, mps, tc, inc, sd, st)
12483      ChessProgramState *cps;
12484      int mps, inc, sd, st;
12485      long tc;
12486 {
12487     char buf[MSG_SIZ];
12488     int seconds;
12489
12490     if( timeControl_2 > 0 ) {
12491         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12492             tc = timeControl_2;
12493         }
12494     }
12495     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12496     inc /= cps->timeOdds;
12497     st  /= cps->timeOdds;
12498
12499     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12500
12501     if (st > 0) {
12502       /* Set exact time per move, normally using st command */
12503       if (cps->stKludge) {
12504         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12505         seconds = st % 60;
12506         if (seconds == 0) {
12507           sprintf(buf, "level 1 %d\n", st/60);
12508         } else {
12509           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12510         }
12511       } else {
12512         sprintf(buf, "st %d\n", st);
12513       }
12514     } else {
12515       /* Set conventional or incremental time control, using level command */
12516       if (seconds == 0) {
12517         /* Note old gnuchess bug -- minutes:seconds used to not work.
12518            Fixed in later versions, but still avoid :seconds
12519            when seconds is 0. */
12520         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12521       } else {
12522         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12523                 seconds, inc/1000);
12524       }
12525     }
12526     SendToProgram(buf, cps);
12527
12528     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12529     /* Orthogonally, limit search to given depth */
12530     if (sd > 0) {
12531       if (cps->sdKludge) {
12532         sprintf(buf, "depth\n%d\n", sd);
12533       } else {
12534         sprintf(buf, "sd %d\n", sd);
12535       }
12536       SendToProgram(buf, cps);
12537     }
12538
12539     if(cps->nps > 0) { /* [HGM] nps */
12540         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12541         else {
12542                 sprintf(buf, "nps %d\n", cps->nps);
12543               SendToProgram(buf, cps);
12544         }
12545     }
12546 }
12547
12548 ChessProgramState *WhitePlayer()
12549 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12550 {
12551     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12552        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12553         return &second;
12554     return &first;
12555 }
12556
12557 void
12558 SendTimeRemaining(cps, machineWhite)
12559      ChessProgramState *cps;
12560      int /*boolean*/ machineWhite;
12561 {
12562     char message[MSG_SIZ];
12563     long time, otime;
12564
12565     /* Note: this routine must be called when the clocks are stopped
12566        or when they have *just* been set or switched; otherwise
12567        it will be off by the time since the current tick started.
12568     */
12569     if (machineWhite) {
12570         time = whiteTimeRemaining / 10;
12571         otime = blackTimeRemaining / 10;
12572     } else {
12573         time = blackTimeRemaining / 10;
12574         otime = whiteTimeRemaining / 10;
12575     }
12576     /* [HGM] translate opponent's time by time-odds factor */
12577     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12578     if (appData.debugMode) {
12579         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12580     }
12581
12582     if (time <= 0) time = 1;
12583     if (otime <= 0) otime = 1;
12584     
12585     sprintf(message, "time %ld\n", time);
12586     SendToProgram(message, cps);
12587
12588     sprintf(message, "otim %ld\n", otime);
12589     SendToProgram(message, cps);
12590 }
12591
12592 int
12593 BoolFeature(p, name, loc, cps)
12594      char **p;
12595      char *name;
12596      int *loc;
12597      ChessProgramState *cps;
12598 {
12599   char buf[MSG_SIZ];
12600   int len = strlen(name);
12601   int val;
12602   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12603     (*p) += len + 1;
12604     sscanf(*p, "%d", &val);
12605     *loc = (val != 0);
12606     while (**p && **p != ' ') (*p)++;
12607     sprintf(buf, "accepted %s\n", name);
12608     SendToProgram(buf, cps);
12609     return TRUE;
12610   }
12611   return FALSE;
12612 }
12613
12614 int
12615 IntFeature(p, name, loc, cps)
12616      char **p;
12617      char *name;
12618      int *loc;
12619      ChessProgramState *cps;
12620 {
12621   char buf[MSG_SIZ];
12622   int len = strlen(name);
12623   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12624     (*p) += len + 1;
12625     sscanf(*p, "%d", loc);
12626     while (**p && **p != ' ') (*p)++;
12627     sprintf(buf, "accepted %s\n", name);
12628     SendToProgram(buf, cps);
12629     return TRUE;
12630   }
12631   return FALSE;
12632 }
12633
12634 int
12635 StringFeature(p, name, loc, cps)
12636      char **p;
12637      char *name;
12638      char loc[];
12639      ChessProgramState *cps;
12640 {
12641   char buf[MSG_SIZ];
12642   int len = strlen(name);
12643   if (strncmp((*p), name, len) == 0
12644       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12645     (*p) += len + 2;
12646     sscanf(*p, "%[^\"]", loc);
12647     while (**p && **p != '\"') (*p)++;
12648     if (**p == '\"') (*p)++;
12649     sprintf(buf, "accepted %s\n", name);
12650     SendToProgram(buf, cps);
12651     return TRUE;
12652   }
12653   return FALSE;
12654 }
12655
12656 int 
12657 ParseOption(Option *opt, ChessProgramState *cps)
12658 // [HGM] options: process the string that defines an engine option, and determine
12659 // name, type, default value, and allowed value range
12660 {
12661         char *p, *q, buf[MSG_SIZ];
12662         int n, min = (-1)<<31, max = 1<<31, def;
12663
12664         if(p = strstr(opt->name, " -spin ")) {
12665             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12666             if(max < min) max = min; // enforce consistency
12667             if(def < min) def = min;
12668             if(def > max) def = max;
12669             opt->value = def;
12670             opt->min = min;
12671             opt->max = max;
12672             opt->type = Spin;
12673         } else if((p = strstr(opt->name, " -slider "))) {
12674             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12675             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12676             if(max < min) max = min; // enforce consistency
12677             if(def < min) def = min;
12678             if(def > max) def = max;
12679             opt->value = def;
12680             opt->min = min;
12681             opt->max = max;
12682             opt->type = Spin; // Slider;
12683         } else if((p = strstr(opt->name, " -string "))) {
12684             opt->textValue = p+9;
12685             opt->type = TextBox;
12686         } else if((p = strstr(opt->name, " -file "))) {
12687             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12688             opt->textValue = p+7;
12689             opt->type = TextBox; // FileName;
12690         } else if((p = strstr(opt->name, " -path "))) {
12691             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12692             opt->textValue = p+7;
12693             opt->type = TextBox; // PathName;
12694         } else if(p = strstr(opt->name, " -check ")) {
12695             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12696             opt->value = (def != 0);
12697             opt->type = CheckBox;
12698         } else if(p = strstr(opt->name, " -combo ")) {
12699             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12700             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12701             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12702             opt->value = n = 0;
12703             while(q = StrStr(q, " /// ")) {
12704                 n++; *q = 0;    // count choices, and null-terminate each of them
12705                 q += 5;
12706                 if(*q == '*') { // remember default, which is marked with * prefix
12707                     q++;
12708                     opt->value = n;
12709                 }
12710                 cps->comboList[cps->comboCnt++] = q;
12711             }
12712             cps->comboList[cps->comboCnt++] = NULL;
12713             opt->max = n + 1;
12714             opt->type = ComboBox;
12715         } else if(p = strstr(opt->name, " -button")) {
12716             opt->type = Button;
12717         } else if(p = strstr(opt->name, " -save")) {
12718             opt->type = SaveButton;
12719         } else return FALSE;
12720         *p = 0; // terminate option name
12721         // now look if the command-line options define a setting for this engine option.
12722         if(cps->optionSettings && cps->optionSettings[0])
12723             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12724         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12725                 sprintf(buf, "option %s", p);
12726                 if(p = strstr(buf, ",")) *p = 0;
12727                 strcat(buf, "\n");
12728                 SendToProgram(buf, cps);
12729         }
12730         return TRUE;
12731 }
12732
12733 void
12734 FeatureDone(cps, val)
12735      ChessProgramState* cps;
12736      int val;
12737 {
12738   DelayedEventCallback cb = GetDelayedEvent();
12739   if ((cb == InitBackEnd3 && cps == &first) ||
12740       (cb == TwoMachinesEventIfReady && cps == &second)) {
12741     CancelDelayedEvent();
12742     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12743   }
12744   cps->initDone = val;
12745 }
12746
12747 /* Parse feature command from engine */
12748 void
12749 ParseFeatures(args, cps)
12750      char* args;
12751      ChessProgramState *cps;  
12752 {
12753   char *p = args;
12754   char *q;
12755   int val;
12756   char buf[MSG_SIZ];
12757
12758   for (;;) {
12759     while (*p == ' ') p++;
12760     if (*p == NULLCHAR) return;
12761
12762     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12763     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12764     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12765     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12766     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12767     if (BoolFeature(&p, "reuse", &val, cps)) {
12768       /* Engine can disable reuse, but can't enable it if user said no */
12769       if (!val) cps->reuse = FALSE;
12770       continue;
12771     }
12772     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12773     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12774       if (gameMode == TwoMachinesPlay) {
12775         DisplayTwoMachinesTitle();
12776       } else {
12777         DisplayTitle("");
12778       }
12779       continue;
12780     }
12781     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12782     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12783     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12784     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12785     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12786     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12787     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12788     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12789     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12790     if (IntFeature(&p, "done", &val, cps)) {
12791       FeatureDone(cps, val);
12792       continue;
12793     }
12794     /* Added by Tord: */
12795     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12796     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12797     /* End of additions by Tord */
12798
12799     /* [HGM] added features: */
12800     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12801     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12802     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12803     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12804     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12805     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12806     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12807         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12808             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12809             SendToProgram(buf, cps);
12810             continue;
12811         }
12812         if(cps->nrOptions >= MAX_OPTIONS) {
12813             cps->nrOptions--;
12814             sprintf(buf, "%s engine has too many options\n", cps->which);
12815             DisplayError(buf, 0);
12816         }
12817         continue;
12818     }
12819     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12820     /* End of additions by HGM */
12821
12822     /* unknown feature: complain and skip */
12823     q = p;
12824     while (*q && *q != '=') q++;
12825     sprintf(buf, "rejected %.*s\n", q-p, p);
12826     SendToProgram(buf, cps);
12827     p = q;
12828     if (*p == '=') {
12829       p++;
12830       if (*p == '\"') {
12831         p++;
12832         while (*p && *p != '\"') p++;
12833         if (*p == '\"') p++;
12834       } else {
12835         while (*p && *p != ' ') p++;
12836       }
12837     }
12838   }
12839
12840 }
12841
12842 void
12843 PeriodicUpdatesEvent(newState)
12844      int newState;
12845 {
12846     if (newState == appData.periodicUpdates)
12847       return;
12848
12849     appData.periodicUpdates=newState;
12850
12851     /* Display type changes, so update it now */
12852 //    DisplayAnalysis();
12853
12854     /* Get the ball rolling again... */
12855     if (newState) {
12856         AnalysisPeriodicEvent(1);
12857         StartAnalysisClock();
12858     }
12859 }
12860
12861 void
12862 PonderNextMoveEvent(newState)
12863      int newState;
12864 {
12865     if (newState == appData.ponderNextMove) return;
12866     if (gameMode == EditPosition) EditPositionDone();
12867     if (newState) {
12868         SendToProgram("hard\n", &first);
12869         if (gameMode == TwoMachinesPlay) {
12870             SendToProgram("hard\n", &second);
12871         }
12872     } else {
12873         SendToProgram("easy\n", &first);
12874         thinkOutput[0] = NULLCHAR;
12875         if (gameMode == TwoMachinesPlay) {
12876             SendToProgram("easy\n", &second);
12877         }
12878     }
12879     appData.ponderNextMove = newState;
12880 }
12881
12882 void
12883 NewSettingEvent(option, command, value)
12884      char *command;
12885      int option, value;
12886 {
12887     char buf[MSG_SIZ];
12888
12889     if (gameMode == EditPosition) EditPositionDone();
12890     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12891     SendToProgram(buf, &first);
12892     if (gameMode == TwoMachinesPlay) {
12893         SendToProgram(buf, &second);
12894     }
12895 }
12896
12897 void
12898 ShowThinkingEvent()
12899 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12900 {
12901     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12902     int newState = appData.showThinking
12903         // [HGM] thinking: other features now need thinking output as well
12904         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12905     
12906     if (oldState == newState) return;
12907     oldState = newState;
12908     if (gameMode == EditPosition) EditPositionDone();
12909     if (oldState) {
12910         SendToProgram("post\n", &first);
12911         if (gameMode == TwoMachinesPlay) {
12912             SendToProgram("post\n", &second);
12913         }
12914     } else {
12915         SendToProgram("nopost\n", &first);
12916         thinkOutput[0] = NULLCHAR;
12917         if (gameMode == TwoMachinesPlay) {
12918             SendToProgram("nopost\n", &second);
12919         }
12920     }
12921 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12922 }
12923
12924 void
12925 AskQuestionEvent(title, question, replyPrefix, which)
12926      char *title; char *question; char *replyPrefix; char *which;
12927 {
12928   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12929   if (pr == NoProc) return;
12930   AskQuestion(title, question, replyPrefix, pr);
12931 }
12932
12933 void
12934 DisplayMove(moveNumber)
12935      int moveNumber;
12936 {
12937     char message[MSG_SIZ];
12938     char res[MSG_SIZ];
12939     char cpThinkOutput[MSG_SIZ];
12940
12941     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12942     
12943     if (moveNumber == forwardMostMove - 1 || 
12944         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12945
12946         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12947
12948         if (strchr(cpThinkOutput, '\n')) {
12949             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12950         }
12951     } else {
12952         *cpThinkOutput = NULLCHAR;
12953     }
12954
12955     /* [AS] Hide thinking from human user */
12956     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12957         *cpThinkOutput = NULLCHAR;
12958         if( thinkOutput[0] != NULLCHAR ) {
12959             int i;
12960
12961             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12962                 cpThinkOutput[i] = '.';
12963             }
12964             cpThinkOutput[i] = NULLCHAR;
12965             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12966         }
12967     }
12968
12969     if (moveNumber == forwardMostMove - 1 &&
12970         gameInfo.resultDetails != NULL) {
12971         if (gameInfo.resultDetails[0] == NULLCHAR) {
12972             sprintf(res, " %s", PGNResult(gameInfo.result));
12973         } else {
12974             sprintf(res, " {%s} %s",
12975                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12976         }
12977     } else {
12978         res[0] = NULLCHAR;
12979     }
12980
12981     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12982         DisplayMessage(res, cpThinkOutput);
12983     } else {
12984         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12985                 WhiteOnMove(moveNumber) ? " " : ".. ",
12986                 parseList[moveNumber], res);
12987         DisplayMessage(message, cpThinkOutput);
12988     }
12989 }
12990
12991 void
12992 DisplayComment(moveNumber, text)
12993      int moveNumber;
12994      char *text;
12995 {
12996     char title[MSG_SIZ];
12997     char buf[8000]; // comment can be long!
12998     int score, depth;
12999
13000     if( appData.autoDisplayComment ) {
13001         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13002             strcpy(title, "Comment");
13003         } else {
13004             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13005                     WhiteOnMove(moveNumber) ? " " : ".. ",
13006                     parseList[moveNumber]);
13007         }
13008         // [HGM] PV info: display PV info together with (or as) comment
13009         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13010             if(text == NULL) text = "";                                           
13011             score = pvInfoList[moveNumber].score;
13012             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13013                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13014             text = buf;
13015         }
13016     } else title[0] = 0;
13017
13018     if (text != NULL)
13019         CommentPopUp(title, text);
13020 }
13021
13022 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13023  * might be busy thinking or pondering.  It can be omitted if your
13024  * gnuchess is configured to stop thinking immediately on any user
13025  * input.  However, that gnuchess feature depends on the FIONREAD
13026  * ioctl, which does not work properly on some flavors of Unix.
13027  */
13028 void
13029 Attention(cps)
13030      ChessProgramState *cps;
13031 {
13032 #if ATTENTION
13033     if (!cps->useSigint) return;
13034     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13035     switch (gameMode) {
13036       case MachinePlaysWhite:
13037       case MachinePlaysBlack:
13038       case TwoMachinesPlay:
13039       case IcsPlayingWhite:
13040       case IcsPlayingBlack:
13041       case AnalyzeMode:
13042       case AnalyzeFile:
13043         /* Skip if we know it isn't thinking */
13044         if (!cps->maybeThinking) return;
13045         if (appData.debugMode)
13046           fprintf(debugFP, "Interrupting %s\n", cps->which);
13047         InterruptChildProcess(cps->pr);
13048         cps->maybeThinking = FALSE;
13049         break;
13050       default:
13051         break;
13052     }
13053 #endif /*ATTENTION*/
13054 }
13055
13056 int
13057 CheckFlags()
13058 {
13059     if (whiteTimeRemaining <= 0) {
13060         if (!whiteFlag) {
13061             whiteFlag = TRUE;
13062             if (appData.icsActive) {
13063                 if (appData.autoCallFlag &&
13064                     gameMode == IcsPlayingBlack && !blackFlag) {
13065                   SendToICS(ics_prefix);
13066                   SendToICS("flag\n");
13067                 }
13068             } else {
13069                 if (blackFlag) {
13070                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13071                 } else {
13072                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13073                     if (appData.autoCallFlag) {
13074                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13075                         return TRUE;
13076                     }
13077                 }
13078             }
13079         }
13080     }
13081     if (blackTimeRemaining <= 0) {
13082         if (!blackFlag) {
13083             blackFlag = TRUE;
13084             if (appData.icsActive) {
13085                 if (appData.autoCallFlag &&
13086                     gameMode == IcsPlayingWhite && !whiteFlag) {
13087                   SendToICS(ics_prefix);
13088                   SendToICS("flag\n");
13089                 }
13090             } else {
13091                 if (whiteFlag) {
13092                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13093                 } else {
13094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13095                     if (appData.autoCallFlag) {
13096                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13097                         return TRUE;
13098                     }
13099                 }
13100             }
13101         }
13102     }
13103     return FALSE;
13104 }
13105
13106 void
13107 CheckTimeControl()
13108 {
13109     if (!appData.clockMode || appData.icsActive ||
13110         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13111
13112     /*
13113      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13114      */
13115     if ( !WhiteOnMove(forwardMostMove) )
13116         /* White made time control */
13117         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13118         /* [HGM] time odds: correct new time quota for time odds! */
13119                                             / WhitePlayer()->timeOdds;
13120       else
13121         /* Black made time control */
13122         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13123                                             / WhitePlayer()->other->timeOdds;
13124 }
13125
13126 void
13127 DisplayBothClocks()
13128 {
13129     int wom = gameMode == EditPosition ?
13130       !blackPlaysFirst : WhiteOnMove(currentMove);
13131     DisplayWhiteClock(whiteTimeRemaining, wom);
13132     DisplayBlackClock(blackTimeRemaining, !wom);
13133 }
13134
13135
13136 /* Timekeeping seems to be a portability nightmare.  I think everyone
13137    has ftime(), but I'm really not sure, so I'm including some ifdefs
13138    to use other calls if you don't.  Clocks will be less accurate if
13139    you have neither ftime nor gettimeofday.
13140 */
13141
13142 /* VS 2008 requires the #include outside of the function */
13143 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13144 #include <sys/timeb.h>
13145 #endif
13146
13147 /* Get the current time as a TimeMark */
13148 void
13149 GetTimeMark(tm)
13150      TimeMark *tm;
13151 {
13152 #if HAVE_GETTIMEOFDAY
13153
13154     struct timeval timeVal;
13155     struct timezone timeZone;
13156
13157     gettimeofday(&timeVal, &timeZone);
13158     tm->sec = (long) timeVal.tv_sec; 
13159     tm->ms = (int) (timeVal.tv_usec / 1000L);
13160
13161 #else /*!HAVE_GETTIMEOFDAY*/
13162 #if HAVE_FTIME
13163
13164 // include <sys/timeb.h> / moved to just above start of function
13165     struct timeb timeB;
13166
13167     ftime(&timeB);
13168     tm->sec = (long) timeB.time;
13169     tm->ms = (int) timeB.millitm;
13170
13171 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13172     tm->sec = (long) time(NULL);
13173     tm->ms = 0;
13174 #endif
13175 #endif
13176 }
13177
13178 /* Return the difference in milliseconds between two
13179    time marks.  We assume the difference will fit in a long!
13180 */
13181 long
13182 SubtractTimeMarks(tm2, tm1)
13183      TimeMark *tm2, *tm1;
13184 {
13185     return 1000L*(tm2->sec - tm1->sec) +
13186            (long) (tm2->ms - tm1->ms);
13187 }
13188
13189
13190 /*
13191  * Code to manage the game clocks.
13192  *
13193  * In tournament play, black starts the clock and then white makes a move.
13194  * We give the human user a slight advantage if he is playing white---the
13195  * clocks don't run until he makes his first move, so it takes zero time.
13196  * Also, we don't account for network lag, so we could get out of sync
13197  * with GNU Chess's clock -- but then, referees are always right.  
13198  */
13199
13200 static TimeMark tickStartTM;
13201 static long intendedTickLength;
13202
13203 long
13204 NextTickLength(timeRemaining)
13205      long timeRemaining;
13206 {
13207     long nominalTickLength, nextTickLength;
13208
13209     if (timeRemaining > 0L && timeRemaining <= 10000L)
13210       nominalTickLength = 100L;
13211     else
13212       nominalTickLength = 1000L;
13213     nextTickLength = timeRemaining % nominalTickLength;
13214     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13215
13216     return nextTickLength;
13217 }
13218
13219 /* Adjust clock one minute up or down */
13220 void
13221 AdjustClock(Boolean which, int dir)
13222 {
13223     if(which) blackTimeRemaining += 60000*dir;
13224     else      whiteTimeRemaining += 60000*dir;
13225     DisplayBothClocks();
13226 }
13227
13228 /* Stop clocks and reset to a fresh time control */
13229 void
13230 ResetClocks() 
13231 {
13232     (void) StopClockTimer();
13233     if (appData.icsActive) {
13234         whiteTimeRemaining = blackTimeRemaining = 0;
13235     } else { /* [HGM] correct new time quote for time odds */
13236         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13237         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13238     }
13239     if (whiteFlag || blackFlag) {
13240         DisplayTitle("");
13241         whiteFlag = blackFlag = FALSE;
13242     }
13243     DisplayBothClocks();
13244 }
13245
13246 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13247
13248 /* Decrement running clock by amount of time that has passed */
13249 void
13250 DecrementClocks()
13251 {
13252     long timeRemaining;
13253     long lastTickLength, fudge;
13254     TimeMark now;
13255
13256     if (!appData.clockMode) return;
13257     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13258         
13259     GetTimeMark(&now);
13260
13261     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13262
13263     /* Fudge if we woke up a little too soon */
13264     fudge = intendedTickLength - lastTickLength;
13265     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13266
13267     if (WhiteOnMove(forwardMostMove)) {
13268         if(whiteNPS >= 0) lastTickLength = 0;
13269         timeRemaining = whiteTimeRemaining -= lastTickLength;
13270         DisplayWhiteClock(whiteTimeRemaining - fudge,
13271                           WhiteOnMove(currentMove));
13272     } else {
13273         if(blackNPS >= 0) lastTickLength = 0;
13274         timeRemaining = blackTimeRemaining -= lastTickLength;
13275         DisplayBlackClock(blackTimeRemaining - fudge,
13276                           !WhiteOnMove(currentMove));
13277     }
13278
13279     if (CheckFlags()) return;
13280         
13281     tickStartTM = now;
13282     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13283     StartClockTimer(intendedTickLength);
13284
13285     /* if the time remaining has fallen below the alarm threshold, sound the
13286      * alarm. if the alarm has sounded and (due to a takeback or time control
13287      * with increment) the time remaining has increased to a level above the
13288      * threshold, reset the alarm so it can sound again. 
13289      */
13290     
13291     if (appData.icsActive && appData.icsAlarm) {
13292
13293         /* make sure we are dealing with the user's clock */
13294         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13295                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13296            )) return;
13297
13298         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13299             alarmSounded = FALSE;
13300         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13301             PlayAlarmSound();
13302             alarmSounded = TRUE;
13303         }
13304     }
13305 }
13306
13307
13308 /* A player has just moved, so stop the previously running
13309    clock and (if in clock mode) start the other one.
13310    We redisplay both clocks in case we're in ICS mode, because
13311    ICS gives us an update to both clocks after every move.
13312    Note that this routine is called *after* forwardMostMove
13313    is updated, so the last fractional tick must be subtracted
13314    from the color that is *not* on move now.
13315 */
13316 void
13317 SwitchClocks()
13318 {
13319     long lastTickLength;
13320     TimeMark now;
13321     int flagged = FALSE;
13322
13323     GetTimeMark(&now);
13324
13325     if (StopClockTimer() && appData.clockMode) {
13326         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13327         if (WhiteOnMove(forwardMostMove)) {
13328             if(blackNPS >= 0) lastTickLength = 0;
13329             blackTimeRemaining -= lastTickLength;
13330            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13331 //         if(pvInfoList[forwardMostMove-1].time == -1)
13332                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13333                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13334         } else {
13335            if(whiteNPS >= 0) lastTickLength = 0;
13336            whiteTimeRemaining -= lastTickLength;
13337            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13338 //         if(pvInfoList[forwardMostMove-1].time == -1)
13339                  pvInfoList[forwardMostMove-1].time = 
13340                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13341         }
13342         flagged = CheckFlags();
13343     }
13344     CheckTimeControl();
13345
13346     if (flagged || !appData.clockMode) return;
13347
13348     switch (gameMode) {
13349       case MachinePlaysBlack:
13350       case MachinePlaysWhite:
13351       case BeginningOfGame:
13352         if (pausing) return;
13353         break;
13354
13355       case EditGame:
13356       case PlayFromGameFile:
13357       case IcsExamining:
13358         return;
13359
13360       default:
13361         break;
13362     }
13363
13364     tickStartTM = now;
13365     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13366       whiteTimeRemaining : blackTimeRemaining);
13367     StartClockTimer(intendedTickLength);
13368 }
13369         
13370
13371 /* Stop both clocks */
13372 void
13373 StopClocks()
13374 {       
13375     long lastTickLength;
13376     TimeMark now;
13377
13378     if (!StopClockTimer()) return;
13379     if (!appData.clockMode) return;
13380
13381     GetTimeMark(&now);
13382
13383     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13384     if (WhiteOnMove(forwardMostMove)) {
13385         if(whiteNPS >= 0) lastTickLength = 0;
13386         whiteTimeRemaining -= lastTickLength;
13387         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13388     } else {
13389         if(blackNPS >= 0) lastTickLength = 0;
13390         blackTimeRemaining -= lastTickLength;
13391         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13392     }
13393     CheckFlags();
13394 }
13395         
13396 /* Start clock of player on move.  Time may have been reset, so
13397    if clock is already running, stop and restart it. */
13398 void
13399 StartClocks()
13400 {
13401     (void) StopClockTimer(); /* in case it was running already */
13402     DisplayBothClocks();
13403     if (CheckFlags()) return;
13404
13405     if (!appData.clockMode) return;
13406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13407
13408     GetTimeMark(&tickStartTM);
13409     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13410       whiteTimeRemaining : blackTimeRemaining);
13411
13412    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13413     whiteNPS = blackNPS = -1; 
13414     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13415        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13416         whiteNPS = first.nps;
13417     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13418        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13419         blackNPS = first.nps;
13420     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13421         whiteNPS = second.nps;
13422     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13423         blackNPS = second.nps;
13424     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13425
13426     StartClockTimer(intendedTickLength);
13427 }
13428
13429 char *
13430 TimeString(ms)
13431      long ms;
13432 {
13433     long second, minute, hour, day;
13434     char *sign = "";
13435     static char buf[32];
13436     
13437     if (ms > 0 && ms <= 9900) {
13438       /* convert milliseconds to tenths, rounding up */
13439       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13440
13441       sprintf(buf, " %03.1f ", tenths/10.0);
13442       return buf;
13443     }
13444
13445     /* convert milliseconds to seconds, rounding up */
13446     /* use floating point to avoid strangeness of integer division
13447        with negative dividends on many machines */
13448     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13449
13450     if (second < 0) {
13451         sign = "-";
13452         second = -second;
13453     }
13454     
13455     day = second / (60 * 60 * 24);
13456     second = second % (60 * 60 * 24);
13457     hour = second / (60 * 60);
13458     second = second % (60 * 60);
13459     minute = second / 60;
13460     second = second % 60;
13461     
13462     if (day > 0)
13463       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13464               sign, day, hour, minute, second);
13465     else if (hour > 0)
13466       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13467     else
13468       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13469     
13470     return buf;
13471 }
13472
13473
13474 /*
13475  * This is necessary because some C libraries aren't ANSI C compliant yet.
13476  */
13477 char *
13478 StrStr(string, match)
13479      char *string, *match;
13480 {
13481     int i, length;
13482     
13483     length = strlen(match);
13484     
13485     for (i = strlen(string) - length; i >= 0; i--, string++)
13486       if (!strncmp(match, string, length))
13487         return string;
13488     
13489     return NULL;
13490 }
13491
13492 char *
13493 StrCaseStr(string, match)
13494      char *string, *match;
13495 {
13496     int i, j, length;
13497     
13498     length = strlen(match);
13499     
13500     for (i = strlen(string) - length; i >= 0; i--, string++) {
13501         for (j = 0; j < length; j++) {
13502             if (ToLower(match[j]) != ToLower(string[j]))
13503               break;
13504         }
13505         if (j == length) return string;
13506     }
13507
13508     return NULL;
13509 }
13510
13511 #ifndef _amigados
13512 int
13513 StrCaseCmp(s1, s2)
13514      char *s1, *s2;
13515 {
13516     char c1, c2;
13517     
13518     for (;;) {
13519         c1 = ToLower(*s1++);
13520         c2 = ToLower(*s2++);
13521         if (c1 > c2) return 1;
13522         if (c1 < c2) return -1;
13523         if (c1 == NULLCHAR) return 0;
13524     }
13525 }
13526
13527
13528 int
13529 ToLower(c)
13530      int c;
13531 {
13532     return isupper(c) ? tolower(c) : c;
13533 }
13534
13535
13536 int
13537 ToUpper(c)
13538      int c;
13539 {
13540     return islower(c) ? toupper(c) : c;
13541 }
13542 #endif /* !_amigados    */
13543
13544 char *
13545 StrSave(s)
13546      char *s;
13547 {
13548     char *ret;
13549
13550     if ((ret = (char *) malloc(strlen(s) + 1))) {
13551         strcpy(ret, s);
13552     }
13553     return ret;
13554 }
13555
13556 char *
13557 StrSavePtr(s, savePtr)
13558      char *s, **savePtr;
13559 {
13560     if (*savePtr) {
13561         free(*savePtr);
13562     }
13563     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13564         strcpy(*savePtr, s);
13565     }
13566     return(*savePtr);
13567 }
13568
13569 char *
13570 PGNDate()
13571 {
13572     time_t clock;
13573     struct tm *tm;
13574     char buf[MSG_SIZ];
13575
13576     clock = time((time_t *)NULL);
13577     tm = localtime(&clock);
13578     sprintf(buf, "%04d.%02d.%02d",
13579             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13580     return StrSave(buf);
13581 }
13582
13583
13584 char *
13585 PositionToFEN(move, overrideCastling)
13586      int move;
13587      char *overrideCastling;
13588 {
13589     int i, j, fromX, fromY, toX, toY;
13590     int whiteToPlay;
13591     char buf[128];
13592     char *p, *q;
13593     int emptycount;
13594     ChessSquare piece;
13595
13596     whiteToPlay = (gameMode == EditPosition) ?
13597       !blackPlaysFirst : (move % 2 == 0);
13598     p = buf;
13599
13600     /* Piece placement data */
13601     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13602         emptycount = 0;
13603         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13604             if (boards[move][i][j] == EmptySquare) {
13605                 emptycount++;
13606             } else { ChessSquare piece = boards[move][i][j];
13607                 if (emptycount > 0) {
13608                     if(emptycount<10) /* [HGM] can be >= 10 */
13609                         *p++ = '0' + emptycount;
13610                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13611                     emptycount = 0;
13612                 }
13613                 if(PieceToChar(piece) == '+') {
13614                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13615                     *p++ = '+';
13616                     piece = (ChessSquare)(DEMOTED piece);
13617                 } 
13618                 *p++ = PieceToChar(piece);
13619                 if(p[-1] == '~') {
13620                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13621                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13622                     *p++ = '~';
13623                 }
13624             }
13625         }
13626         if (emptycount > 0) {
13627             if(emptycount<10) /* [HGM] can be >= 10 */
13628                 *p++ = '0' + emptycount;
13629             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13630             emptycount = 0;
13631         }
13632         *p++ = '/';
13633     }
13634     *(p - 1) = ' ';
13635
13636     /* [HGM] print Crazyhouse or Shogi holdings */
13637     if( gameInfo.holdingsWidth ) {
13638         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13639         q = p;
13640         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13641             piece = boards[move][i][BOARD_WIDTH-1];
13642             if( piece != EmptySquare )
13643               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13644                   *p++ = PieceToChar(piece);
13645         }
13646         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13647             piece = boards[move][BOARD_HEIGHT-i-1][0];
13648             if( piece != EmptySquare )
13649               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13650                   *p++ = PieceToChar(piece);
13651         }
13652
13653         if( q == p ) *p++ = '-';
13654         *p++ = ']';
13655         *p++ = ' ';
13656     }
13657
13658     /* Active color */
13659     *p++ = whiteToPlay ? 'w' : 'b';
13660     *p++ = ' ';
13661
13662   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13663     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13664   } else {
13665   if(nrCastlingRights) {
13666      q = p;
13667      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13668        /* [HGM] write directly from rights */
13669            if(castlingRights[move][2] >= 0 &&
13670               castlingRights[move][0] >= 0   )
13671                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13672            if(castlingRights[move][2] >= 0 &&
13673               castlingRights[move][1] >= 0   )
13674                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13675            if(castlingRights[move][5] >= 0 &&
13676               castlingRights[move][3] >= 0   )
13677                 *p++ = castlingRights[move][3] + AAA;
13678            if(castlingRights[move][5] >= 0 &&
13679               castlingRights[move][4] >= 0   )
13680                 *p++ = castlingRights[move][4] + AAA;
13681      } else {
13682
13683         /* [HGM] write true castling rights */
13684         if( nrCastlingRights == 6 ) {
13685             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13686                castlingRights[move][2] >= 0  ) *p++ = 'K';
13687             if(castlingRights[move][1] == BOARD_LEFT &&
13688                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13689             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13690                castlingRights[move][5] >= 0  ) *p++ = 'k';
13691             if(castlingRights[move][4] == BOARD_LEFT &&
13692                castlingRights[move][5] >= 0  ) *p++ = 'q';
13693         }
13694      }
13695      if (q == p) *p++ = '-'; /* No castling rights */
13696      *p++ = ' ';
13697   }
13698
13699   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13700      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13701     /* En passant target square */
13702     if (move > backwardMostMove) {
13703         fromX = moveList[move - 1][0] - AAA;
13704         fromY = moveList[move - 1][1] - ONE;
13705         toX = moveList[move - 1][2] - AAA;
13706         toY = moveList[move - 1][3] - ONE;
13707         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13708             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13709             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13710             fromX == toX) {
13711             /* 2-square pawn move just happened */
13712             *p++ = toX + AAA;
13713             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13714         } else {
13715             *p++ = '-';
13716         }
13717     } else if(move == backwardMostMove) {
13718         // [HGM] perhaps we should always do it like this, and forget the above?
13719         if(epStatus[move] >= 0) {
13720             *p++ = epStatus[move] + AAA;
13721             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13722         } else {
13723             *p++ = '-';
13724         }
13725     } else {
13726         *p++ = '-';
13727     }
13728     *p++ = ' ';
13729   }
13730   }
13731
13732     /* [HGM] find reversible plies */
13733     {   int i = 0, j=move;
13734
13735         if (appData.debugMode) { int k;
13736             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13737             for(k=backwardMostMove; k<=forwardMostMove; k++)
13738                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13739
13740         }
13741
13742         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13743         if( j == backwardMostMove ) i += initialRulePlies;
13744         sprintf(p, "%d ", i);
13745         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13746     }
13747     /* Fullmove number */
13748     sprintf(p, "%d", (move / 2) + 1);
13749     
13750     return StrSave(buf);
13751 }
13752
13753 Boolean
13754 ParseFEN(board, blackPlaysFirst, fen)
13755     Board board;
13756      int *blackPlaysFirst;
13757      char *fen;
13758 {
13759     int i, j;
13760     char *p;
13761     int emptycount;
13762     ChessSquare piece;
13763
13764     p = fen;
13765
13766     /* [HGM] by default clear Crazyhouse holdings, if present */
13767     if(gameInfo.holdingsWidth) {
13768        for(i=0; i<BOARD_HEIGHT; i++) {
13769            board[i][0]             = EmptySquare; /* black holdings */
13770            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13771            board[i][1]             = (ChessSquare) 0; /* black counts */
13772            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13773        }
13774     }
13775
13776     /* Piece placement data */
13777     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13778         j = 0;
13779         for (;;) {
13780             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13781                 if (*p == '/') p++;
13782                 emptycount = gameInfo.boardWidth - j;
13783                 while (emptycount--)
13784                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13785                 break;
13786 #if(BOARD_SIZE >= 10)
13787             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13788                 p++; emptycount=10;
13789                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13790                 while (emptycount--)
13791                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13792 #endif
13793             } else if (isdigit(*p)) {
13794                 emptycount = *p++ - '0';
13795                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13796                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13797                 while (emptycount--)
13798                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13799             } else if (*p == '+' || isalpha(*p)) {
13800                 if (j >= gameInfo.boardWidth) return FALSE;
13801                 if(*p=='+') {
13802                     piece = CharToPiece(*++p);
13803                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13804                     piece = (ChessSquare) (PROMOTED piece ); p++;
13805                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13806                 } else piece = CharToPiece(*p++);
13807
13808                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13809                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13810                     piece = (ChessSquare) (PROMOTED piece);
13811                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13812                     p++;
13813                 }
13814                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13815             } else {
13816                 return FALSE;
13817             }
13818         }
13819     }
13820     while (*p == '/' || *p == ' ') p++;
13821
13822     /* [HGM] look for Crazyhouse holdings here */
13823     while(*p==' ') p++;
13824     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13825         if(*p == '[') p++;
13826         if(*p == '-' ) *p++; /* empty holdings */ else {
13827             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13828             /* if we would allow FEN reading to set board size, we would   */
13829             /* have to add holdings and shift the board read so far here   */
13830             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13831                 *p++;
13832                 if((int) piece >= (int) BlackPawn ) {
13833                     i = (int)piece - (int)BlackPawn;
13834                     i = PieceToNumber((ChessSquare)i);
13835                     if( i >= gameInfo.holdingsSize ) return FALSE;
13836                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13837                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13838                 } else {
13839                     i = (int)piece - (int)WhitePawn;
13840                     i = PieceToNumber((ChessSquare)i);
13841                     if( i >= gameInfo.holdingsSize ) return FALSE;
13842                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13843                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13844                 }
13845             }
13846         }
13847         if(*p == ']') *p++;
13848     }
13849
13850     while(*p == ' ') p++;
13851
13852     /* Active color */
13853     switch (*p++) {
13854       case 'w':
13855         *blackPlaysFirst = FALSE;
13856         break;
13857       case 'b': 
13858         *blackPlaysFirst = TRUE;
13859         break;
13860       default:
13861         return FALSE;
13862     }
13863
13864     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13865     /* return the extra info in global variiables             */
13866
13867     /* set defaults in case FEN is incomplete */
13868     FENepStatus = EP_UNKNOWN;
13869     for(i=0; i<nrCastlingRights; i++ ) {
13870         FENcastlingRights[i] =
13871             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13872     }   /* assume possible unless obviously impossible */
13873     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13874     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13875     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13876     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13877     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13878     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13879     FENrulePlies = 0;
13880
13881     while(*p==' ') p++;
13882     if(nrCastlingRights) {
13883       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13884           /* castling indicator present, so default becomes no castlings */
13885           for(i=0; i<nrCastlingRights; i++ ) {
13886                  FENcastlingRights[i] = -1;
13887           }
13888       }
13889       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13890              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13891              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13892              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13893         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13894
13895         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13896             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13897             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13898         }
13899         switch(c) {
13900           case'K':
13901               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13902               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13903               FENcastlingRights[2] = whiteKingFile;
13904               break;
13905           case'Q':
13906               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13907               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13908               FENcastlingRights[2] = whiteKingFile;
13909               break;
13910           case'k':
13911               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13912               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13913               FENcastlingRights[5] = blackKingFile;
13914               break;
13915           case'q':
13916               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13917               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13918               FENcastlingRights[5] = blackKingFile;
13919           case '-':
13920               break;
13921           default: /* FRC castlings */
13922               if(c >= 'a') { /* black rights */
13923                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13924                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13925                   if(i == BOARD_RGHT) break;
13926                   FENcastlingRights[5] = i;
13927                   c -= AAA;
13928                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13929                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13930                   if(c > i)
13931                       FENcastlingRights[3] = c;
13932                   else
13933                       FENcastlingRights[4] = c;
13934               } else { /* white rights */
13935                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13936                     if(board[0][i] == WhiteKing) break;
13937                   if(i == BOARD_RGHT) break;
13938                   FENcastlingRights[2] = i;
13939                   c -= AAA - 'a' + 'A';
13940                   if(board[0][c] >= WhiteKing) break;
13941                   if(c > i)
13942                       FENcastlingRights[0] = c;
13943                   else
13944                       FENcastlingRights[1] = c;
13945               }
13946         }
13947       }
13948     if (appData.debugMode) {
13949         fprintf(debugFP, "FEN castling rights:");
13950         for(i=0; i<nrCastlingRights; i++)
13951         fprintf(debugFP, " %d", FENcastlingRights[i]);
13952         fprintf(debugFP, "\n");
13953     }
13954
13955       while(*p==' ') p++;
13956     }
13957
13958     /* read e.p. field in games that know e.p. capture */
13959     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13960        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13961       if(*p=='-') {
13962         p++; FENepStatus = EP_NONE;
13963       } else {
13964          char c = *p++ - AAA;
13965
13966          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13967          if(*p >= '0' && *p <='9') *p++;
13968          FENepStatus = c;
13969       }
13970     }
13971
13972
13973     if(sscanf(p, "%d", &i) == 1) {
13974         FENrulePlies = i; /* 50-move ply counter */
13975         /* (The move number is still ignored)    */
13976     }
13977
13978     return TRUE;
13979 }
13980       
13981 void
13982 EditPositionPasteFEN(char *fen)
13983 {
13984   if (fen != NULL) {
13985     Board initial_position;
13986
13987     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13988       DisplayError(_("Bad FEN position in clipboard"), 0);
13989       return ;
13990     } else {
13991       int savedBlackPlaysFirst = blackPlaysFirst;
13992       EditPositionEvent();
13993       blackPlaysFirst = savedBlackPlaysFirst;
13994       CopyBoard(boards[0], initial_position);
13995           /* [HGM] copy FEN attributes as well */
13996           {   int i;
13997               initialRulePlies = FENrulePlies;
13998               epStatus[0] = FENepStatus;
13999               for( i=0; i<nrCastlingRights; i++ )
14000                   castlingRights[0][i] = FENcastlingRights[i];
14001           }
14002       EditPositionDone();
14003       DisplayBothClocks();
14004       DrawPosition(FALSE, boards[currentMove]);
14005     }
14006   }
14007 }
14008
14009 static char cseq[12] = "\\   ";
14010
14011 Boolean set_cont_sequence(char *new_seq)
14012 {
14013     int len;
14014     Boolean ret;
14015
14016     // handle bad attempts to set the sequence
14017         if (!new_seq)
14018                 return 0; // acceptable error - no debug
14019
14020     len = strlen(new_seq);
14021     ret = (len > 0) && (len < sizeof(cseq));
14022     if (ret)
14023         strcpy(cseq, new_seq);
14024     else if (appData.debugMode)
14025         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14026     return ret;
14027 }
14028
14029 /*
14030     reformat a source message so words don't cross the width boundary.  internal
14031     newlines are not removed.  returns the wrapped size (no null character unless
14032     included in source message).  If dest is NULL, only calculate the size required
14033     for the dest buffer.  lp argument indicats line position upon entry, and it's
14034     passed back upon exit.
14035 */
14036 int wrap(char *dest, char *src, int count, int width, int *lp)
14037 {
14038     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14039
14040     cseq_len = strlen(cseq);
14041     old_line = line = *lp;
14042     ansi = len = clen = 0;
14043
14044     for (i=0; i < count; i++)
14045     {
14046         if (src[i] == '\033')
14047             ansi = 1;
14048
14049         // if we hit the width, back up
14050         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14051         {
14052             // store i & len in case the word is too long
14053             old_i = i, old_len = len;
14054
14055             // find the end of the last word
14056             while (i && src[i] != ' ' && src[i] != '\n')
14057             {
14058                 i--;
14059                 len--;
14060             }
14061
14062             // word too long?  restore i & len before splitting it
14063             if ((old_i-i+clen) >= width)
14064             {
14065                 i = old_i;
14066                 len = old_len;
14067             }
14068
14069             // extra space?
14070             if (i && src[i-1] == ' ')
14071                 len--;
14072
14073             if (src[i] != ' ' && src[i] != '\n')
14074             {
14075                 i--;
14076                 if (len)
14077                     len--;
14078             }
14079
14080             // now append the newline and continuation sequence
14081             if (dest)
14082                 dest[len] = '\n';
14083             len++;
14084             if (dest)
14085                 strncpy(dest+len, cseq, cseq_len);
14086             len += cseq_len;
14087             line = cseq_len;
14088             clen = cseq_len;
14089             continue;
14090         }
14091
14092         if (dest)
14093             dest[len] = src[i];
14094         len++;
14095         if (!ansi)
14096             line++;
14097         if (src[i] == '\n')
14098             line = 0;
14099         if (src[i] == 'm')
14100             ansi = 0;
14101     }
14102     if (dest && appData.debugMode)
14103     {
14104         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14105             count, width, line, len, *lp);
14106         show_bytes(debugFP, src, count);
14107         fprintf(debugFP, "\ndest: ");
14108         show_bytes(debugFP, dest, len);
14109         fprintf(debugFP, "\n");
14110     }
14111     *lp = dest ? line : old_line;
14112
14113     return len;
14114 }