fix for bug #8847: moving backward while examining on FICS not reported to engine
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     set_cont_sequence(appData.wrapContSeq);
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        break;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(TRUE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(TRUE); }
2016 }
2017
2018 static int loggedOn = FALSE;
2019
2020 /*-- Game start info cache: --*/
2021 int gs_gamenum;
2022 char gs_kind[MSG_SIZ];
2023 static char player1Name[128] = "";
2024 static char player2Name[128] = "";
2025 static char cont_seq[] = "\n\\   ";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2029
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2032
2033 void
2034 read_from_ics(isr, closure, data, count, error)
2035      InputSourceRef isr;
2036      VOIDSTAR closure;
2037      char *data;
2038      int count;
2039      int error;
2040 {
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2050     
2051     static int started = STARTED_NONE;
2052     static char parse[20000];
2053     static int parse_pos = 0;
2054     static char buf[BUF_SIZE + 1];
2055     static int firstTime = TRUE, intfSet = FALSE;
2056     static ColorClass prevColor = ColorNormal;
2057     static int savingComment = FALSE;
2058     static int cmatch = 0; // continuation sequence match
2059     char *bp;
2060     char str[500];
2061     int i, oldi;
2062     int buf_len;
2063     int next_out;
2064     int tkind;
2065     int backup;    /* [DM] For zippy color lines */
2066     char *p;
2067     char talker[MSG_SIZ]; // [HGM] chat
2068     int channel;
2069
2070     if (appData.debugMode) {
2071       if (!error) {
2072         fprintf(debugFP, "<ICS: ");
2073         show_bytes(debugFP, data, count);
2074         fprintf(debugFP, "\n");
2075       }
2076     }
2077
2078     if (appData.debugMode) { int f = forwardMostMove;
2079         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2080                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2081     }
2082     if (count > 0) {
2083         /* If last read ended with a partial line that we couldn't parse,
2084            prepend it to the new read and try again. */
2085         if (leftover_len > 0) {
2086             for (i=0; i<leftover_len; i++)
2087               buf[i] = buf[leftover_start + i];
2088         }
2089
2090     /* copy new characters into the buffer */
2091     bp = buf + leftover_len;
2092     buf_len=leftover_len;
2093     for (i=0; i<count; i++)
2094     {
2095         // ignore these
2096         if (data[i] == '\r')
2097             continue;
2098
2099         // join lines split by ICS?
2100         if (!appData.noJoin)
2101         {
2102             /*
2103                 Joining just consists of finding matches against the
2104                 continuation sequence, and discarding that sequence
2105                 if found instead of copying it.  So, until a match
2106                 fails, there's nothing to do since it might be the
2107                 complete sequence, and thus, something we don't want
2108                 copied.
2109             */
2110             if (data[i] == cont_seq[cmatch])
2111             {
2112                 cmatch++;
2113                 if (cmatch == strlen(cont_seq))
2114                 {
2115                     cmatch = 0; // complete match.  just reset the counter
2116
2117                     /*
2118                         it's possible for the ICS to not include the space
2119                         at the end of the last word, making our [correct]
2120                         join operation fuse two separate words.  the server
2121                         does this when the space occurs at the width setting.
2122                     */
2123                     if (!buf_len || buf[buf_len-1] != ' ')
2124                     {
2125                         *bp++ = ' ';
2126                         buf_len++;
2127                     }
2128                 }
2129                 continue;
2130             }
2131             else if (cmatch)
2132             {
2133                 /*
2134                     match failed, so we have to copy what matched before
2135                     falling through and copying this character.  In reality,
2136                     this will only ever be just the newline character, but
2137                     it doesn't hurt to be precise.
2138                 */
2139                 strncpy(bp, cont_seq, cmatch);
2140                 bp += cmatch;
2141                 buf_len += cmatch;
2142                 cmatch = 0;
2143             }
2144         }
2145
2146         // copy this char
2147         *bp++ = data[i];
2148         buf_len++;
2149     }
2150
2151         buf[buf_len] = NULLCHAR;
2152         next_out = leftover_len;
2153         leftover_start = 0;
2154         
2155         i = 0;
2156         while (i < buf_len) {
2157             /* Deal with part of the TELNET option negotiation
2158                protocol.  We refuse to do anything beyond the
2159                defaults, except that we allow the WILL ECHO option,
2160                which ICS uses to turn off password echoing when we are
2161                directly connected to it.  We reject this option
2162                if localLineEditing mode is on (always on in xboard)
2163                and we are talking to port 23, which might be a real
2164                telnet server that will try to keep WILL ECHO on permanently.
2165              */
2166             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2167                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2168                 unsigned char option;
2169                 oldi = i;
2170                 switch ((unsigned char) buf[++i]) {
2171                   case TN_WILL:
2172                     if (appData.debugMode)
2173                       fprintf(debugFP, "\n<WILL ");
2174                     switch (option = (unsigned char) buf[++i]) {
2175                       case TN_ECHO:
2176                         if (appData.debugMode)
2177                           fprintf(debugFP, "ECHO ");
2178                         /* Reply only if this is a change, according
2179                            to the protocol rules. */
2180                         if (remoteEchoOption) break;
2181                         if (appData.localLineEditing &&
2182                             atoi(appData.icsPort) == TN_PORT) {
2183                             TelnetRequest(TN_DONT, TN_ECHO);
2184                         } else {
2185                             EchoOff();
2186                             TelnetRequest(TN_DO, TN_ECHO);
2187                             remoteEchoOption = TRUE;
2188                         }
2189                         break;
2190                       default:
2191                         if (appData.debugMode)
2192                           fprintf(debugFP, "%d ", option);
2193                         /* Whatever this is, we don't want it. */
2194                         TelnetRequest(TN_DONT, option);
2195                         break;
2196                     }
2197                     break;
2198                   case TN_WONT:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<WONT ");
2201                     switch (option = (unsigned char) buf[++i]) {
2202                       case TN_ECHO:
2203                         if (appData.debugMode)
2204                           fprintf(debugFP, "ECHO ");
2205                         /* Reply only if this is a change, according
2206                            to the protocol rules. */
2207                         if (!remoteEchoOption) break;
2208                         EchoOn();
2209                         TelnetRequest(TN_DONT, TN_ECHO);
2210                         remoteEchoOption = FALSE;
2211                         break;
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", (unsigned char) option);
2215                         /* Whatever this is, it must already be turned
2216                            off, because we never agree to turn on
2217                            anything non-default, so according to the
2218                            protocol rules, we don't reply. */
2219                         break;
2220                     }
2221                     break;
2222                   case TN_DO:
2223                     if (appData.debugMode)
2224                       fprintf(debugFP, "\n<DO ");
2225                     switch (option = (unsigned char) buf[++i]) {
2226                       default:
2227                         /* Whatever this is, we refuse to do it. */
2228                         if (appData.debugMode)
2229                           fprintf(debugFP, "%d ", option);
2230                         TelnetRequest(TN_WONT, option);
2231                         break;
2232                     }
2233                     break;
2234                   case TN_DONT:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<DONT ");
2237                     switch (option = (unsigned char) buf[++i]) {
2238                       default:
2239                         if (appData.debugMode)
2240                           fprintf(debugFP, "%d ", option);
2241                         /* Whatever this is, we are already not doing
2242                            it, because we never agree to do anything
2243                            non-default, so according to the protocol
2244                            rules, we don't reply. */
2245                         break;
2246                     }
2247                     break;
2248                   case TN_IAC:
2249                     if (appData.debugMode)
2250                       fprintf(debugFP, "\n<IAC ");
2251                     /* Doubled IAC; pass it through */
2252                     i--;
2253                     break;
2254                   default:
2255                     if (appData.debugMode)
2256                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2257                     /* Drop all other telnet commands on the floor */
2258                     break;
2259                 }
2260                 if (oldi > next_out)
2261                   SendToPlayer(&buf[next_out], oldi - next_out);
2262                 if (++i > next_out)
2263                   next_out = i;
2264                 continue;
2265             }
2266                 
2267             /* OK, this at least will *usually* work */
2268             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2269                 loggedOn = TRUE;
2270             }
2271             
2272             if (loggedOn && !intfSet) {
2273                 if (ics_type == ICS_ICC) {
2274                   sprintf(str,
2275                           "/set-quietly interface %s\n/set-quietly style 12\n",
2276                           programVersion);
2277                 } else if (ics_type == ICS_CHESSNET) {
2278                   sprintf(str, "/style 12\n");
2279                 } else {
2280                   strcpy(str, "alias $ @\n$set interface ");
2281                   strcat(str, programVersion);
2282                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2283 #ifdef WIN32
2284                   strcat(str, "$iset nohighlight 1\n");
2285 #endif
2286                   strcat(str, "$iset lock 1\n$style 12\n");
2287                 }
2288                 SendToICS(str);
2289                 NotifyFrontendLogin();
2290                 intfSet = TRUE;
2291             }
2292
2293             if (started == STARTED_COMMENT) {
2294                 /* Accumulate characters in comment */
2295                 parse[parse_pos++] = buf[i];
2296                 if (buf[i] == '\n') {
2297                     parse[parse_pos] = NULLCHAR;
2298                     if(chattingPartner>=0) {
2299                         char mess[MSG_SIZ];
2300                         sprintf(mess, "%s%s", talker, parse);
2301                         OutputChatMessage(chattingPartner, mess);
2302                         chattingPartner = -1;
2303                     } else
2304                     if(!suppressKibitz) // [HGM] kibitz
2305                         AppendComment(forwardMostMove, StripHighlight(parse));
2306                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2307                         int nrDigit = 0, nrAlph = 0, i;
2308                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2309                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2310                         parse[parse_pos] = NULLCHAR;
2311                         // try to be smart: if it does not look like search info, it should go to
2312                         // ICS interaction window after all, not to engine-output window.
2313                         for(i=0; i<parse_pos; i++) { // count letters and digits
2314                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2315                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2316                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2317                         }
2318                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2319                             int depth=0; float score;
2320                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2321                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2322                                 pvInfoList[forwardMostMove-1].depth = depth;
2323                                 pvInfoList[forwardMostMove-1].score = 100*score;
2324                             }
2325                             OutputKibitz(suppressKibitz, parse);
2326                         } else {
2327                             char tmp[MSG_SIZ];
2328                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2329                             SendToPlayer(tmp, strlen(tmp));
2330                         }
2331                     }
2332                     started = STARTED_NONE;
2333                 } else {
2334                     /* Don't match patterns against characters in chatter */
2335                     i++;
2336                     continue;
2337                 }
2338             }
2339             if (started == STARTED_CHATTER) {
2340                 if (buf[i] != '\n') {
2341                     /* Don't match patterns against characters in chatter */
2342                     i++;
2343                     continue;
2344                 }
2345                 started = STARTED_NONE;
2346             }
2347
2348             /* Kludge to deal with rcmd protocol */
2349             if (firstTime && looking_at(buf, &i, "\001*")) {
2350                 DisplayFatalError(&buf[1], 0, 1);
2351                 continue;
2352             } else {
2353                 firstTime = FALSE;
2354             }
2355
2356             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2357                 ics_type = ICS_ICC;
2358                 ics_prefix = "/";
2359                 if (appData.debugMode)
2360                   fprintf(debugFP, "ics_type %d\n", ics_type);
2361                 continue;
2362             }
2363             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2364                 ics_type = ICS_FICS;
2365                 ics_prefix = "$";
2366                 if (appData.debugMode)
2367                   fprintf(debugFP, "ics_type %d\n", ics_type);
2368                 continue;
2369             }
2370             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2371                 ics_type = ICS_CHESSNET;
2372                 ics_prefix = "/";
2373                 if (appData.debugMode)
2374                   fprintf(debugFP, "ics_type %d\n", ics_type);
2375                 continue;
2376             }
2377
2378             if (!loggedOn &&
2379                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2380                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2381                  looking_at(buf, &i, "will be \"*\""))) {
2382               strcpy(ics_handle, star_match[0]);
2383               continue;
2384             }
2385
2386             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2387               char buf[MSG_SIZ];
2388               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2389               DisplayIcsInteractionTitle(buf);
2390               have_set_title = TRUE;
2391             }
2392
2393             /* skip finger notes */
2394             if (started == STARTED_NONE &&
2395                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2396                  (buf[i] == '1' && buf[i+1] == '0')) &&
2397                 buf[i+2] == ':' && buf[i+3] == ' ') {
2398               started = STARTED_CHATTER;
2399               i += 3;
2400               continue;
2401             }
2402
2403             /* skip formula vars */
2404             if (started == STARTED_NONE &&
2405                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2406               started = STARTED_CHATTER;
2407               i += 3;
2408               continue;
2409             }
2410
2411             oldi = i;
2412             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2413             if (appData.autoKibitz && started == STARTED_NONE && 
2414                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2415                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2416                 if(looking_at(buf, &i, "* kibitzes: ") &&
2417                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2418                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2419                         suppressKibitz = TRUE;
2420                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2421                                 && (gameMode == IcsPlayingWhite)) ||
2422                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2423                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2424                             started = STARTED_CHATTER; // own kibitz we simply discard
2425                         else {
2426                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2427                             parse_pos = 0; parse[0] = NULLCHAR;
2428                             savingComment = TRUE;
2429                             suppressKibitz = gameMode != IcsObserving ? 2 :
2430                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2431                         } 
2432                         continue;
2433                 } else
2434                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2435                     started = STARTED_CHATTER;
2436                     suppressKibitz = TRUE;
2437                 }
2438             } // [HGM] kibitz: end of patch
2439
2440 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2441
2442             // [HGM] chat: intercept tells by users for which we have an open chat window
2443             channel = -1;
2444             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2445                                            looking_at(buf, &i, "* whispers:") ||
2446                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2447                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2448                 int p;
2449                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2450                 chattingPartner = -1;
2451
2452                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2453                 for(p=0; p<MAX_CHAT; p++) {
2454                     if(channel == atoi(chatPartner[p])) {
2455                     talker[0] = '['; strcat(talker, "]");
2456                     chattingPartner = p; break;
2457                     }
2458                 } else
2459                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2460                 for(p=0; p<MAX_CHAT; p++) {
2461                     if(!strcmp("WHISPER", chatPartner[p])) {
2462                         talker[0] = '['; strcat(talker, "]");
2463                         chattingPartner = p; break;
2464                     }
2465                 }
2466                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2467                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2468                     talker[0] = 0;
2469                     chattingPartner = p; break;
2470                 }
2471                 if(chattingPartner<0) i = oldi; else {
2472                     started = STARTED_COMMENT;
2473                     parse_pos = 0; parse[0] = NULLCHAR;
2474                     savingComment = TRUE;
2475                     suppressKibitz = TRUE;
2476                 }
2477             } // [HGM] chat: end of patch
2478
2479             if (appData.zippyTalk || appData.zippyPlay) {
2480                 /* [DM] Backup address for color zippy lines */
2481                 backup = i;
2482 #if ZIPPY
2483        #ifdef WIN32
2484                if (loggedOn == TRUE)
2485                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2486                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2487        #else
2488                 if (ZippyControl(buf, &i) ||
2489                     ZippyConverse(buf, &i) ||
2490                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2491                       loggedOn = TRUE;
2492                       if (!appData.colorize) continue;
2493                 }
2494        #endif
2495 #endif
2496             } // [DM] 'else { ' deleted
2497                 if (
2498                     /* Regular tells and says */
2499                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2500                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2501                     looking_at(buf, &i, "* says: ") ||
2502                     /* Don't color "message" or "messages" output */
2503                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2504                     looking_at(buf, &i, "*. * at *:*: ") ||
2505                     looking_at(buf, &i, "--* (*:*): ") ||
2506                     /* Message notifications (same color as tells) */
2507                     looking_at(buf, &i, "* has left a message ") ||
2508                     looking_at(buf, &i, "* just sent you a message:\n") ||
2509                     /* Whispers and kibitzes */
2510                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2511                     looking_at(buf, &i, "* kibitzes: ") ||
2512                     /* Channel tells */
2513                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2514
2515                   if (tkind == 1 && strchr(star_match[0], ':')) {
2516                       /* Avoid "tells you:" spoofs in channels */
2517                      tkind = 3;
2518                   }
2519                   if (star_match[0][0] == NULLCHAR ||
2520                       strchr(star_match[0], ' ') ||
2521                       (tkind == 3 && strchr(star_match[1], ' '))) {
2522                     /* Reject bogus matches */
2523                     i = oldi;
2524                   } else {
2525                     if (appData.colorize) {
2526                       if (oldi > next_out) {
2527                         SendToPlayer(&buf[next_out], oldi - next_out);
2528                         next_out = oldi;
2529                       }
2530                       switch (tkind) {
2531                       case 1:
2532                         Colorize(ColorTell, FALSE);
2533                         curColor = ColorTell;
2534                         break;
2535                       case 2:
2536                         Colorize(ColorKibitz, FALSE);
2537                         curColor = ColorKibitz;
2538                         break;
2539                       case 3:
2540                         p = strrchr(star_match[1], '(');
2541                         if (p == NULL) {
2542                           p = star_match[1];
2543                         } else {
2544                           p++;
2545                         }
2546                         if (atoi(p) == 1) {
2547                           Colorize(ColorChannel1, FALSE);
2548                           curColor = ColorChannel1;
2549                         } else {
2550                           Colorize(ColorChannel, FALSE);
2551                           curColor = ColorChannel;
2552                         }
2553                         break;
2554                       case 5:
2555                         curColor = ColorNormal;
2556                         break;
2557                       }
2558                     }
2559                     if (started == STARTED_NONE && appData.autoComment &&
2560                         (gameMode == IcsObserving ||
2561                          gameMode == IcsPlayingWhite ||
2562                          gameMode == IcsPlayingBlack)) {
2563                       parse_pos = i - oldi;
2564                       memcpy(parse, &buf[oldi], parse_pos);
2565                       parse[parse_pos] = NULLCHAR;
2566                       started = STARTED_COMMENT;
2567                       savingComment = TRUE;
2568                     } else {
2569                       started = STARTED_CHATTER;
2570                       savingComment = FALSE;
2571                     }
2572                     loggedOn = TRUE;
2573                     continue;
2574                   }
2575                 }
2576
2577                 if (looking_at(buf, &i, "* s-shouts: ") ||
2578                     looking_at(buf, &i, "* c-shouts: ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorSShout, FALSE);
2585                         curColor = ColorSShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at(buf, &i, "--->")) {
2593                     loggedOn = TRUE;
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "* shouts: ") ||
2598                     looking_at(buf, &i, "--> ")) {
2599                     if (appData.colorize) {
2600                         if (oldi > next_out) {
2601                             SendToPlayer(&buf[next_out], oldi - next_out);
2602                             next_out = oldi;
2603                         }
2604                         Colorize(ColorShout, FALSE);
2605                         curColor = ColorShout;
2606                     }
2607                     loggedOn = TRUE;
2608                     started = STARTED_CHATTER;
2609                     continue;
2610                 }
2611
2612                 if (looking_at( buf, &i, "Challenge:")) {
2613                     if (appData.colorize) {
2614                         if (oldi > next_out) {
2615                             SendToPlayer(&buf[next_out], oldi - next_out);
2616                             next_out = oldi;
2617                         }
2618                         Colorize(ColorChallenge, FALSE);
2619                         curColor = ColorChallenge;
2620                     }
2621                     loggedOn = TRUE;
2622                     continue;
2623                 }
2624
2625                 if (looking_at(buf, &i, "* offers you") ||
2626                     looking_at(buf, &i, "* offers to be") ||
2627                     looking_at(buf, &i, "* would like to") ||
2628                     looking_at(buf, &i, "* requests to") ||
2629                     looking_at(buf, &i, "Your opponent offers") ||
2630                     looking_at(buf, &i, "Your opponent requests")) {
2631
2632                     if (appData.colorize) {
2633                         if (oldi > next_out) {
2634                             SendToPlayer(&buf[next_out], oldi - next_out);
2635                             next_out = oldi;
2636                         }
2637                         Colorize(ColorRequest, FALSE);
2638                         curColor = ColorRequest;
2639                     }
2640                     continue;
2641                 }
2642
2643                 if (looking_at(buf, &i, "* (*) seeking")) {
2644                     if (appData.colorize) {
2645                         if (oldi > next_out) {
2646                             SendToPlayer(&buf[next_out], oldi - next_out);
2647                             next_out = oldi;
2648                         }
2649                         Colorize(ColorSeek, FALSE);
2650                         curColor = ColorSeek;
2651                     }
2652                     continue;
2653             }
2654
2655             if (looking_at(buf, &i, "\\   ")) {
2656                 if (prevColor != ColorNormal) {
2657                     if (oldi > next_out) {
2658                         SendToPlayer(&buf[next_out], oldi - next_out);
2659                         next_out = oldi;
2660                     }
2661                     Colorize(prevColor, TRUE);
2662                     curColor = prevColor;
2663                 }
2664                 if (savingComment) {
2665                     parse_pos = i - oldi;
2666                     memcpy(parse, &buf[oldi], parse_pos);
2667                     parse[parse_pos] = NULLCHAR;
2668                     started = STARTED_COMMENT;
2669                 } else {
2670                     started = STARTED_CHATTER;
2671                 }
2672                 continue;
2673             }
2674
2675             if (looking_at(buf, &i, "Black Strength :") ||
2676                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2677                 looking_at(buf, &i, "<10>") ||
2678                 looking_at(buf, &i, "#@#")) {
2679                 /* Wrong board style */
2680                 loggedOn = TRUE;
2681                 SendToICS(ics_prefix);
2682                 SendToICS("set style 12\n");
2683                 SendToICS(ics_prefix);
2684                 SendToICS("refresh\n");
2685                 continue;
2686             }
2687             
2688             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2689                 ICSInitScript();
2690                 have_sent_ICS_logon = 1;
2691                 continue;
2692             }
2693               
2694             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2695                 (looking_at(buf, &i, "\n<12> ") ||
2696                  looking_at(buf, &i, "<12> "))) {
2697                 loggedOn = TRUE;
2698                 if (oldi > next_out) {
2699                     SendToPlayer(&buf[next_out], oldi - next_out);
2700                 }
2701                 next_out = i;
2702                 started = STARTED_BOARD;
2703                 parse_pos = 0;
2704                 continue;
2705             }
2706
2707             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2708                 looking_at(buf, &i, "<b1> ")) {
2709                 if (oldi > next_out) {
2710                     SendToPlayer(&buf[next_out], oldi - next_out);
2711                 }
2712                 next_out = i;
2713                 started = STARTED_HOLDINGS;
2714                 parse_pos = 0;
2715                 continue;
2716             }
2717
2718             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2719                 loggedOn = TRUE;
2720                 /* Header for a move list -- first line */
2721
2722                 switch (ics_getting_history) {
2723                   case H_FALSE:
2724                     switch (gameMode) {
2725                       case IcsIdle:
2726                       case BeginningOfGame:
2727                         /* User typed "moves" or "oldmoves" while we
2728                            were idle.  Pretend we asked for these
2729                            moves and soak them up so user can step
2730                            through them and/or save them.
2731                            */
2732                         Reset(TRUE, TRUE);
2733                         gameMode = IcsObserving;
2734                         ModeHighlight();
2735                         ics_gamenum = -1;
2736                         ics_getting_history = H_GOT_UNREQ_HEADER;
2737                         break;
2738                       case EditGame: /*?*/
2739                       case EditPosition: /*?*/
2740                         /* Should above feature work in these modes too? */
2741                         /* For now it doesn't */
2742                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2743                         break;
2744                       default:
2745                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2746                         break;
2747                     }
2748                     break;
2749                   case H_REQUESTED:
2750                     /* Is this the right one? */
2751                     if (gameInfo.white && gameInfo.black &&
2752                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2753                         strcmp(gameInfo.black, star_match[2]) == 0) {
2754                         /* All is well */
2755                         ics_getting_history = H_GOT_REQ_HEADER;
2756                     }
2757                     break;
2758                   case H_GOT_REQ_HEADER:
2759                   case H_GOT_UNREQ_HEADER:
2760                   case H_GOT_UNWANTED_HEADER:
2761                   case H_GETTING_MOVES:
2762                     /* Should not happen */
2763                     DisplayError(_("Error gathering move list: two headers"), 0);
2764                     ics_getting_history = H_FALSE;
2765                     break;
2766                 }
2767
2768                 /* Save player ratings into gameInfo if needed */
2769                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2770                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2771                     (gameInfo.whiteRating == -1 ||
2772                      gameInfo.blackRating == -1)) {
2773
2774                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2775                     gameInfo.blackRating = string_to_rating(star_match[3]);
2776                     if (appData.debugMode)
2777                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2778                               gameInfo.whiteRating, gameInfo.blackRating);
2779                 }
2780                 continue;
2781             }
2782
2783             if (looking_at(buf, &i,
2784               "* * match, initial time: * minute*, increment: * second")) {
2785                 /* Header for a move list -- second line */
2786                 /* Initial board will follow if this is a wild game */
2787                 if (gameInfo.event != NULL) free(gameInfo.event);
2788                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2789                 gameInfo.event = StrSave(str);
2790                 /* [HGM] we switched variant. Translate boards if needed. */
2791                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2792                 continue;
2793             }
2794
2795             if (looking_at(buf, &i, "Move  ")) {
2796                 /* Beginning of a move list */
2797                 switch (ics_getting_history) {
2798                   case H_FALSE:
2799                     /* Normally should not happen */
2800                     /* Maybe user hit reset while we were parsing */
2801                     break;
2802                   case H_REQUESTED:
2803                     /* Happens if we are ignoring a move list that is not
2804                      * the one we just requested.  Common if the user
2805                      * tries to observe two games without turning off
2806                      * getMoveList */
2807                     break;
2808                   case H_GETTING_MOVES:
2809                     /* Should not happen */
2810                     DisplayError(_("Error gathering move list: nested"), 0);
2811                     ics_getting_history = H_FALSE;
2812                     break;
2813                   case H_GOT_REQ_HEADER:
2814                     ics_getting_history = H_GETTING_MOVES;
2815                     started = STARTED_MOVES;
2816                     parse_pos = 0;
2817                     if (oldi > next_out) {
2818                         SendToPlayer(&buf[next_out], oldi - next_out);
2819                     }
2820                     break;
2821                   case H_GOT_UNREQ_HEADER:
2822                     ics_getting_history = H_GETTING_MOVES;
2823                     started = STARTED_MOVES_NOHIDE;
2824                     parse_pos = 0;
2825                     break;
2826                   case H_GOT_UNWANTED_HEADER:
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                 }
2830                 continue;
2831             }                           
2832             
2833             if (looking_at(buf, &i, "% ") ||
2834                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2835                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2836                 savingComment = FALSE;
2837                 switch (started) {
2838                   case STARTED_MOVES:
2839                   case STARTED_MOVES_NOHIDE:
2840                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2841                     parse[parse_pos + i - oldi] = NULLCHAR;
2842                     ParseGameHistory(parse);
2843 #if ZIPPY
2844                     if (appData.zippyPlay && first.initDone) {
2845                         FeedMovesToProgram(&first, forwardMostMove);
2846                         if (gameMode == IcsPlayingWhite) {
2847                             if (WhiteOnMove(forwardMostMove)) {
2848                                 if (first.sendTime) {
2849                                   if (first.useColors) {
2850                                     SendToProgram("black\n", &first); 
2851                                   }
2852                                   SendTimeRemaining(&first, TRUE);
2853                                 }
2854                                 if (first.useColors) {
2855                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2856                                 }
2857                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2858                                 first.maybeThinking = TRUE;
2859                             } else {
2860                                 if (first.usePlayother) {
2861                                   if (first.sendTime) {
2862                                     SendTimeRemaining(&first, TRUE);
2863                                   }
2864                                   SendToProgram("playother\n", &first);
2865                                   firstMove = FALSE;
2866                                 } else {
2867                                   firstMove = TRUE;
2868                                 }
2869                             }
2870                         } else if (gameMode == IcsPlayingBlack) {
2871                             if (!WhiteOnMove(forwardMostMove)) {
2872                                 if (first.sendTime) {
2873                                   if (first.useColors) {
2874                                     SendToProgram("white\n", &first);
2875                                   }
2876                                   SendTimeRemaining(&first, FALSE);
2877                                 }
2878                                 if (first.useColors) {
2879                                   SendToProgram("black\n", &first);
2880                                 }
2881                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2882                                 first.maybeThinking = TRUE;
2883                             } else {
2884                                 if (first.usePlayother) {
2885                                   if (first.sendTime) {
2886                                     SendTimeRemaining(&first, FALSE);
2887                                   }
2888                                   SendToProgram("playother\n", &first);
2889                                   firstMove = FALSE;
2890                                 } else {
2891                                   firstMove = TRUE;
2892                                 }
2893                             }
2894                         }                       
2895                     }
2896 #endif
2897                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2898                         /* Moves came from oldmoves or moves command
2899                            while we weren't doing anything else.
2900                            */
2901                         currentMove = forwardMostMove;
2902                         ClearHighlights();/*!!could figure this out*/
2903                         flipView = appData.flipView;
2904                         DrawPosition(FALSE, boards[currentMove]);
2905                         DisplayBothClocks();
2906                         sprintf(str, "%s vs. %s",
2907                                 gameInfo.white, gameInfo.black);
2908                         DisplayTitle(str);
2909                         gameMode = IcsIdle;
2910                     } else {
2911                         /* Moves were history of an active game */
2912                         if (gameInfo.resultDetails != NULL) {
2913                             free(gameInfo.resultDetails);
2914                             gameInfo.resultDetails = NULL;
2915                         }
2916                     }
2917                     HistorySet(parseList, backwardMostMove,
2918                                forwardMostMove, currentMove-1);
2919                     DisplayMove(currentMove - 1);
2920                     if (started == STARTED_MOVES) next_out = i;
2921                     started = STARTED_NONE;
2922                     ics_getting_history = H_FALSE;
2923                     break;
2924
2925                   case STARTED_OBSERVE:
2926                     started = STARTED_NONE;
2927                     SendToICS(ics_prefix);
2928                     SendToICS("refresh\n");
2929                     break;
2930
2931                   default:
2932                     break;
2933                 }
2934                 if(bookHit) { // [HGM] book: simulate book reply
2935                     static char bookMove[MSG_SIZ]; // a bit generous?
2936
2937                     programStats.nodes = programStats.depth = programStats.time = 
2938                     programStats.score = programStats.got_only_move = 0;
2939                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2940
2941                     strcpy(bookMove, "move ");
2942                     strcat(bookMove, bookHit);
2943                     HandleMachineMove(bookMove, &first);
2944                 }
2945                 continue;
2946             }
2947             
2948             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2949                  started == STARTED_HOLDINGS ||
2950                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2951                 /* Accumulate characters in move list or board */
2952                 parse[parse_pos++] = buf[i];
2953             }
2954             
2955             /* Start of game messages.  Mostly we detect start of game
2956                when the first board image arrives.  On some versions
2957                of the ICS, though, we need to do a "refresh" after starting
2958                to observe in order to get the current board right away. */
2959             if (looking_at(buf, &i, "Adding game * to observation list")) {
2960                 started = STARTED_OBSERVE;
2961                 continue;
2962             }
2963
2964             /* Handle auto-observe */
2965             if (appData.autoObserve &&
2966                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2967                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2968                 char *player;
2969                 /* Choose the player that was highlighted, if any. */
2970                 if (star_match[0][0] == '\033' ||
2971                     star_match[1][0] != '\033') {
2972                     player = star_match[0];
2973                 } else {
2974                     player = star_match[2];
2975                 }
2976                 sprintf(str, "%sobserve %s\n",
2977                         ics_prefix, StripHighlightAndTitle(player));
2978                 SendToICS(str);
2979
2980                 /* Save ratings from notify string */
2981                 strcpy(player1Name, star_match[0]);
2982                 player1Rating = string_to_rating(star_match[1]);
2983                 strcpy(player2Name, star_match[2]);
2984                 player2Rating = string_to_rating(star_match[3]);
2985
2986                 if (appData.debugMode)
2987                   fprintf(debugFP, 
2988                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2989                           player1Name, player1Rating,
2990                           player2Name, player2Rating);
2991
2992                 continue;
2993             }
2994
2995             /* Deal with automatic examine mode after a game,
2996                and with IcsObserving -> IcsExamining transition */
2997             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2998                 looking_at(buf, &i, "has made you an examiner of game *")) {
2999
3000                 int gamenum = atoi(star_match[0]);
3001                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3002                     gamenum == ics_gamenum) {
3003                     /* We were already playing or observing this game;
3004                        no need to refetch history */
3005                     gameMode = IcsExamining;
3006                     if (pausing) {
3007                         pauseExamForwardMostMove = forwardMostMove;
3008                     } else if (currentMove < forwardMostMove) {
3009                         ForwardInner(forwardMostMove);
3010                     }
3011                 } else {
3012                     /* I don't think this case really can happen */
3013                     SendToICS(ics_prefix);
3014                     SendToICS("refresh\n");
3015                 }
3016                 continue;
3017             }    
3018             
3019             /* Error messages */
3020 //          if (ics_user_moved) {
3021             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3022                 if (looking_at(buf, &i, "Illegal move") ||
3023                     looking_at(buf, &i, "Not a legal move") ||
3024                     looking_at(buf, &i, "Your king is in check") ||
3025                     looking_at(buf, &i, "It isn't your turn") ||
3026                     looking_at(buf, &i, "It is not your move")) {
3027                     /* Illegal move */
3028                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3029                         currentMove = --forwardMostMove;
3030                         DisplayMove(currentMove - 1); /* before DMError */
3031                         DrawPosition(FALSE, boards[currentMove]);
3032                         SwitchClocks();
3033                         DisplayBothClocks();
3034                     }
3035                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3036                     ics_user_moved = 0;
3037                     continue;
3038                 }
3039             }
3040
3041             if (looking_at(buf, &i, "still have time") ||
3042                 looking_at(buf, &i, "not out of time") ||
3043                 looking_at(buf, &i, "either player is out of time") ||
3044                 looking_at(buf, &i, "has timeseal; checking")) {
3045                 /* We must have called his flag a little too soon */
3046                 whiteFlag = blackFlag = FALSE;
3047                 continue;
3048             }
3049
3050             if (looking_at(buf, &i, "added * seconds to") ||
3051                 looking_at(buf, &i, "seconds were added to")) {
3052                 /* Update the clocks */
3053                 SendToICS(ics_prefix);
3054                 SendToICS("refresh\n");
3055                 continue;
3056             }
3057
3058             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3059                 ics_clock_paused = TRUE;
3060                 StopClocks();
3061                 continue;
3062             }
3063
3064             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3065                 ics_clock_paused = FALSE;
3066                 StartClocks();
3067                 continue;
3068             }
3069
3070             /* Grab player ratings from the Creating: message.
3071                Note we have to check for the special case when
3072                the ICS inserts things like [white] or [black]. */
3073             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3074                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3075                 /* star_matches:
3076                    0    player 1 name (not necessarily white)
3077                    1    player 1 rating
3078                    2    empty, white, or black (IGNORED)
3079                    3    player 2 name (not necessarily black)
3080                    4    player 2 rating
3081                    
3082                    The names/ratings are sorted out when the game
3083                    actually starts (below).
3084                 */
3085                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3086                 player1Rating = string_to_rating(star_match[1]);
3087                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3088                 player2Rating = string_to_rating(star_match[4]);
3089
3090                 if (appData.debugMode)
3091                   fprintf(debugFP, 
3092                           "Ratings from 'Creating:' %s %d, %s %d\n",
3093                           player1Name, player1Rating,
3094                           player2Name, player2Rating);
3095
3096                 continue;
3097             }
3098             
3099             /* Improved generic start/end-of-game messages */
3100             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3101                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3102                 /* If tkind == 0: */
3103                 /* star_match[0] is the game number */
3104                 /*           [1] is the white player's name */
3105                 /*           [2] is the black player's name */
3106                 /* For end-of-game: */
3107                 /*           [3] is the reason for the game end */
3108                 /*           [4] is a PGN end game-token, preceded by " " */
3109                 /* For start-of-game: */
3110                 /*           [3] begins with "Creating" or "Continuing" */
3111                 /*           [4] is " *" or empty (don't care). */
3112                 int gamenum = atoi(star_match[0]);
3113                 char *whitename, *blackname, *why, *endtoken;
3114                 ChessMove endtype = (ChessMove) 0;
3115
3116                 if (tkind == 0) {
3117                   whitename = star_match[1];
3118                   blackname = star_match[2];
3119                   why = star_match[3];
3120                   endtoken = star_match[4];
3121                 } else {
3122                   whitename = star_match[1];
3123                   blackname = star_match[3];
3124                   why = star_match[5];
3125                   endtoken = star_match[6];
3126                 }
3127
3128                 /* Game start messages */
3129                 if (strncmp(why, "Creating ", 9) == 0 ||
3130                     strncmp(why, "Continuing ", 11) == 0) {
3131                     gs_gamenum = gamenum;
3132                     strcpy(gs_kind, strchr(why, ' ') + 1);
3133 #if ZIPPY
3134                     if (appData.zippyPlay) {
3135                         ZippyGameStart(whitename, blackname);
3136                     }
3137 #endif /*ZIPPY*/
3138                     continue;
3139                 }
3140
3141                 /* Game end messages */
3142                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3143                     ics_gamenum != gamenum) {
3144                     continue;
3145                 }
3146                 while (endtoken[0] == ' ') endtoken++;
3147                 switch (endtoken[0]) {
3148                   case '*':
3149                   default:
3150                     endtype = GameUnfinished;
3151                     break;
3152                   case '0':
3153                     endtype = BlackWins;
3154                     break;
3155                   case '1':
3156                     if (endtoken[1] == '/')
3157                       endtype = GameIsDrawn;
3158                     else
3159                       endtype = WhiteWins;
3160                     break;
3161                 }
3162                 GameEnds(endtype, why, GE_ICS);
3163 #if ZIPPY
3164                 if (appData.zippyPlay && first.initDone) {
3165                     ZippyGameEnd(endtype, why);
3166                     if (first.pr == NULL) {
3167                       /* Start the next process early so that we'll
3168                          be ready for the next challenge */
3169                       StartChessProgram(&first);
3170                     }
3171                     /* Send "new" early, in case this command takes
3172                        a long time to finish, so that we'll be ready
3173                        for the next challenge. */
3174                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3175                     Reset(TRUE, TRUE);
3176                 }
3177 #endif /*ZIPPY*/
3178                 continue;
3179             }
3180
3181             if (looking_at(buf, &i, "Removing game * from observation") ||
3182                 looking_at(buf, &i, "no longer observing game *") ||
3183                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3184                 if (gameMode == IcsObserving &&
3185                     atoi(star_match[0]) == ics_gamenum)
3186                   {
3187                       /* icsEngineAnalyze */
3188                       if (appData.icsEngineAnalyze) {
3189                             ExitAnalyzeMode();
3190                             ModeHighlight();
3191                       }
3192                       StopClocks();
3193                       gameMode = IcsIdle;
3194                       ics_gamenum = -1;
3195                       ics_user_moved = FALSE;
3196                   }
3197                 continue;
3198             }
3199
3200             if (looking_at(buf, &i, "no longer examining game *")) {
3201                 if (gameMode == IcsExamining &&
3202                     atoi(star_match[0]) == ics_gamenum)
3203                   {
3204                       gameMode = IcsIdle;
3205                       ics_gamenum = -1;
3206                       ics_user_moved = FALSE;
3207                   }
3208                 continue;
3209             }
3210
3211             /* Advance leftover_start past any newlines we find,
3212                so only partial lines can get reparsed */
3213             if (looking_at(buf, &i, "\n")) {
3214                 prevColor = curColor;
3215                 if (curColor != ColorNormal) {
3216                     if (oldi > next_out) {
3217                         SendToPlayer(&buf[next_out], oldi - next_out);
3218                         next_out = oldi;
3219                     }
3220                     Colorize(ColorNormal, FALSE);
3221                     curColor = ColorNormal;
3222                 }
3223                 if (started == STARTED_BOARD) {
3224                     started = STARTED_NONE;
3225                     parse[parse_pos] = NULLCHAR;
3226                     ParseBoard12(parse);
3227                     ics_user_moved = 0;
3228
3229                     /* Send premove here */
3230                     if (appData.premove) {
3231                       char str[MSG_SIZ];
3232                       if (currentMove == 0 &&
3233                           gameMode == IcsPlayingWhite &&
3234                           appData.premoveWhite) {
3235                         sprintf(str, "%s%s\n", ics_prefix,
3236                                 appData.premoveWhiteText);
3237                         if (appData.debugMode)
3238                           fprintf(debugFP, "Sending premove:\n");
3239                         SendToICS(str);
3240                       } else if (currentMove == 1 &&
3241                                  gameMode == IcsPlayingBlack &&
3242                                  appData.premoveBlack) {
3243                         sprintf(str, "%s%s\n", ics_prefix,
3244                                 appData.premoveBlackText);
3245                         if (appData.debugMode)
3246                           fprintf(debugFP, "Sending premove:\n");
3247                         SendToICS(str);
3248                       } else if (gotPremove) {
3249                         gotPremove = 0;
3250                         ClearPremoveHighlights();
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                           UserMoveEvent(premoveFromX, premoveFromY, 
3254                                         premoveToX, premoveToY, 
3255                                         premovePromoChar);
3256                       }
3257                     }
3258
3259                     /* Usually suppress following prompt */
3260                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261                         if (looking_at(buf, &i, "*% ")) {
3262                             savingComment = FALSE;
3263                         }
3264                     }
3265                     next_out = i;
3266                 } else if (started == STARTED_HOLDINGS) {
3267                     int gamenum;
3268                     char new_piece[MSG_SIZ];
3269                     started = STARTED_NONE;
3270                     parse[parse_pos] = NULLCHAR;
3271                     if (appData.debugMode)
3272                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3273                                                         parse, currentMove);
3274                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3275                         gamenum == ics_gamenum) {
3276                         if (gameInfo.variant == VariantNormal) {
3277                           /* [HGM] We seem to switch variant during a game!
3278                            * Presumably no holdings were displayed, so we have
3279                            * to move the position two files to the right to
3280                            * create room for them!
3281                            */
3282                           VariantClass newVariant;
3283                           switch(gameInfo.boardWidth) { // base guess on board width
3284                                 case 9:  newVariant = VariantShogi; break;
3285                                 case 10: newVariant = VariantGreat; break;
3286                                 default: newVariant = VariantCrazyhouse; break;
3287                           }
3288                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3289                           /* Get a move list just to see the header, which
3290                              will tell us whether this is really bug or zh */
3291                           if (ics_getting_history == H_FALSE) {
3292                             ics_getting_history = H_REQUESTED;
3293                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3294                             SendToICS(str);
3295                           }
3296                         }
3297                         new_piece[0] = NULLCHAR;
3298                         sscanf(parse, "game %d white [%s black [%s <- %s",
3299                                &gamenum, white_holding, black_holding,
3300                                new_piece);
3301                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3302                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3303                         /* [HGM] copy holdings to board holdings area */
3304                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3305                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3306 #if ZIPPY
3307                         if (appData.zippyPlay && first.initDone) {
3308                             ZippyHoldings(white_holding, black_holding,
3309                                           new_piece);
3310                         }
3311 #endif /*ZIPPY*/
3312                         if (tinyLayout || smallLayout) {
3313                             char wh[16], bh[16];
3314                             PackHolding(wh, white_holding);
3315                             PackHolding(bh, black_holding);
3316                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3317                                     gameInfo.white, gameInfo.black);
3318                         } else {
3319                             sprintf(str, "%s [%s] vs. %s [%s]",
3320                                     gameInfo.white, white_holding,
3321                                     gameInfo.black, black_holding);
3322                         }
3323
3324                         DrawPosition(FALSE, boards[currentMove]);
3325                         DisplayTitle(str);
3326                     }
3327                     /* Suppress following prompt */
3328                     if (looking_at(buf, &i, "*% ")) {
3329                         savingComment = FALSE;
3330                     }
3331                     next_out = i;
3332                 }
3333                 continue;
3334             }
3335
3336             i++;                /* skip unparsed character and loop back */
3337         }
3338         
3339         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3340             started != STARTED_HOLDINGS && i > next_out) {
3341             SendToPlayer(&buf[next_out], i - next_out);
3342             next_out = i;
3343         }
3344         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3345         
3346         leftover_len = buf_len - leftover_start;
3347         /* if buffer ends with something we couldn't parse,
3348            reparse it after appending the next read */
3349         
3350     } else if (count == 0) {
3351         RemoveInputSource(isr);
3352         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3353     } else {
3354         DisplayFatalError(_("Error reading from ICS"), error, 1);
3355     }
3356 }
3357
3358
3359 /* Board style 12 looks like this:
3360    
3361    <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
3362    
3363  * The "<12> " is stripped before it gets to this routine.  The two
3364  * trailing 0's (flip state and clock ticking) are later addition, and
3365  * some chess servers may not have them, or may have only the first.
3366  * Additional trailing fields may be added in the future.  
3367  */
3368
3369 #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"
3370
3371 #define RELATION_OBSERVING_PLAYED    0
3372 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3373 #define RELATION_PLAYING_MYMOVE      1
3374 #define RELATION_PLAYING_NOTMYMOVE  -1
3375 #define RELATION_EXAMINING           2
3376 #define RELATION_ISOLATED_BOARD     -3
3377 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3378
3379 void
3380 ParseBoard12(string)
3381      char *string;
3382
3383     GameMode newGameMode;
3384     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3385     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3386     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3387     char to_play, board_chars[200];
3388     char move_str[500], str[500], elapsed_time[500];
3389     char black[32], white[32];
3390     Board board;
3391     int prevMove = currentMove;
3392     int ticking = 2;
3393     ChessMove moveType;
3394     int fromX, fromY, toX, toY;
3395     char promoChar;
3396     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3397     char *bookHit = NULL; // [HGM] book
3398     Boolean weird = FALSE;
3399
3400     fromX = fromY = toX = toY = -1;
3401     
3402     newGame = FALSE;
3403
3404     if (appData.debugMode)
3405       fprintf(debugFP, _("Parsing board: %s\n"), string);
3406
3407     move_str[0] = NULLCHAR;
3408     elapsed_time[0] = NULLCHAR;
3409     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3410         int  i = 0, j;
3411         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3412             if(string[i] == ' ') { ranks++; files = 0; }
3413             else files++;
3414             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3415             i++;
3416         }
3417         for(j = 0; j <i; j++) board_chars[j] = string[j];
3418         board_chars[i] = '\0';
3419         string += i + 1;
3420     }
3421     n = sscanf(string, PATTERN, &to_play, &double_push,
3422                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3423                &gamenum, white, black, &relation, &basetime, &increment,
3424                &white_stren, &black_stren, &white_time, &black_time,
3425                &moveNum, str, elapsed_time, move_str, &ics_flip,
3426                &ticking);
3427
3428    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3429                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3430      /* [HGM] We seem to switch variant during a game!
3431       * Try to guess new variant from board size
3432       */
3433           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3434           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3435           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3436           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3437           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3438           if(!weird) newVariant = VariantNormal;
3439           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3440           /* Get a move list just to see the header, which
3441              will tell us whether this is really bug or zh */
3442           if (ics_getting_history == H_FALSE) {
3443             ics_getting_history = H_REQUESTED;
3444             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3445             SendToICS(str);
3446           }
3447     }
3448
3449     if (n < 21) {
3450         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3451         DisplayError(str, 0);
3452         return;
3453     }
3454
3455     /* Convert the move number to internal form */
3456     moveNum = (moveNum - 1) * 2;
3457     if (to_play == 'B') moveNum++;
3458     if (moveNum >= MAX_MOVES) {
3459       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460                         0, 1);
3461       return;
3462     }
3463     
3464     switch (relation) {
3465       case RELATION_OBSERVING_PLAYED:
3466       case RELATION_OBSERVING_STATIC:
3467         if (gamenum == -1) {
3468             /* Old ICC buglet */
3469             relation = RELATION_OBSERVING_STATIC;
3470         }
3471         newGameMode = IcsObserving;
3472         break;
3473       case RELATION_PLAYING_MYMOVE:
3474       case RELATION_PLAYING_NOTMYMOVE:
3475         newGameMode =
3476           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3477             IcsPlayingWhite : IcsPlayingBlack;
3478         break;
3479       case RELATION_EXAMINING:
3480         newGameMode = IcsExamining;
3481         break;
3482       case RELATION_ISOLATED_BOARD:
3483       default:
3484         /* Just display this board.  If user was doing something else,
3485            we will forget about it until the next board comes. */ 
3486         newGameMode = IcsIdle;
3487         break;
3488       case RELATION_STARTING_POSITION:
3489         newGameMode = gameMode;
3490         break;
3491     }
3492     
3493     /* Modify behavior for initial board display on move listing
3494        of wild games.
3495        */
3496     switch (ics_getting_history) {
3497       case H_FALSE:
3498       case H_REQUESTED:
3499         break;
3500       case H_GOT_REQ_HEADER:
3501       case H_GOT_UNREQ_HEADER:
3502         /* This is the initial position of the current game */
3503         gamenum = ics_gamenum;
3504         moveNum = 0;            /* old ICS bug workaround */
3505         if (to_play == 'B') {
3506           startedFromSetupPosition = TRUE;
3507           blackPlaysFirst = TRUE;
3508           moveNum = 1;
3509           if (forwardMostMove == 0) forwardMostMove = 1;
3510           if (backwardMostMove == 0) backwardMostMove = 1;
3511           if (currentMove == 0) currentMove = 1;
3512         }
3513         newGameMode = gameMode;
3514         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3515         break;
3516       case H_GOT_UNWANTED_HEADER:
3517         /* This is an initial board that we don't want */
3518         return;
3519       case H_GETTING_MOVES:
3520         /* Should not happen */
3521         DisplayError(_("Error gathering move list: extra board"), 0);
3522         ics_getting_history = H_FALSE;
3523         return;
3524     }
3525     
3526     /* Take action if this is the first board of a new game, or of a
3527        different game than is currently being displayed.  */
3528     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3529         relation == RELATION_ISOLATED_BOARD) {
3530         
3531         /* Forget the old game and get the history (if any) of the new one */
3532         if (gameMode != BeginningOfGame) {
3533           Reset(TRUE, TRUE);
3534         }
3535         newGame = TRUE;
3536         if (appData.autoRaiseBoard) BoardToTop();
3537         prevMove = -3;
3538         if (gamenum == -1) {
3539             newGameMode = IcsIdle;
3540         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3541                    appData.getMoveList) {
3542             /* Need to get game history */
3543             ics_getting_history = H_REQUESTED;
3544             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3545             SendToICS(str);
3546         }
3547         
3548         /* Initially flip the board to have black on the bottom if playing
3549            black or if the ICS flip flag is set, but let the user change
3550            it with the Flip View button. */
3551         flipView = appData.autoFlipView ? 
3552           (newGameMode == IcsPlayingBlack) || ics_flip :
3553           appData.flipView;
3554         
3555         /* Done with values from previous mode; copy in new ones */
3556         gameMode = newGameMode;
3557         ModeHighlight();
3558         ics_gamenum = gamenum;
3559         if (gamenum == gs_gamenum) {
3560             int klen = strlen(gs_kind);
3561             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3562             sprintf(str, "ICS %s", gs_kind);
3563             gameInfo.event = StrSave(str);
3564         } else {
3565             gameInfo.event = StrSave("ICS game");
3566         }
3567         gameInfo.site = StrSave(appData.icsHost);
3568         gameInfo.date = PGNDate();
3569         gameInfo.round = StrSave("-");
3570         gameInfo.white = StrSave(white);
3571         gameInfo.black = StrSave(black);
3572         timeControl = basetime * 60 * 1000;
3573         timeControl_2 = 0;
3574         timeIncrement = increment * 1000;
3575         movesPerSession = 0;
3576         gameInfo.timeControl = TimeControlTagValue();
3577         VariantSwitch(board, StringToVariant(gameInfo.event) );
3578   if (appData.debugMode) {
3579     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3580     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3581     setbuf(debugFP, NULL);
3582   }
3583
3584         gameInfo.outOfBook = NULL;
3585         
3586         /* Do we have the ratings? */
3587         if (strcmp(player1Name, white) == 0 &&
3588             strcmp(player2Name, black) == 0) {
3589             if (appData.debugMode)
3590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3591                       player1Rating, player2Rating);
3592             gameInfo.whiteRating = player1Rating;
3593             gameInfo.blackRating = player2Rating;
3594         } else if (strcmp(player2Name, white) == 0 &&
3595                    strcmp(player1Name, black) == 0) {
3596             if (appData.debugMode)
3597               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3598                       player2Rating, player1Rating);
3599             gameInfo.whiteRating = player2Rating;
3600             gameInfo.blackRating = player1Rating;
3601         }
3602         player1Name[0] = player2Name[0] = NULLCHAR;
3603
3604         /* Silence shouts if requested */
3605         if (appData.quietPlay &&
3606             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3607             SendToICS(ics_prefix);
3608             SendToICS("set shout 0\n");
3609         }
3610     }
3611     
3612     /* Deal with midgame name changes */
3613     if (!newGame) {
3614         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3615             if (gameInfo.white) free(gameInfo.white);
3616             gameInfo.white = StrSave(white);
3617         }
3618         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3619             if (gameInfo.black) free(gameInfo.black);
3620             gameInfo.black = StrSave(black);
3621         }
3622     }
3623     
3624     /* Throw away game result if anything actually changes in examine mode */
3625     if (gameMode == IcsExamining && !newGame) {
3626         gameInfo.result = GameUnfinished;
3627         if (gameInfo.resultDetails != NULL) {
3628             free(gameInfo.resultDetails);
3629             gameInfo.resultDetails = NULL;
3630         }
3631     }
3632     
3633     /* In pausing && IcsExamining mode, we ignore boards coming
3634        in if they are in a different variation than we are. */
3635     if (pauseExamInvalid) return;
3636     if (pausing && gameMode == IcsExamining) {
3637         if (moveNum <= pauseExamForwardMostMove) {
3638             pauseExamInvalid = TRUE;
3639             forwardMostMove = pauseExamForwardMostMove;
3640             return;
3641         }
3642     }
3643     
3644   if (appData.debugMode) {
3645     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3646   }
3647     /* Parse the board */
3648     for (k = 0; k < ranks; k++) {
3649       for (j = 0; j < files; j++)
3650         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3651       if(gameInfo.holdingsWidth > 1) {
3652            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3653            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3654       }
3655     }
3656     CopyBoard(boards[moveNum], board);
3657     if (moveNum == 0) {
3658         startedFromSetupPosition =
3659           !CompareBoards(board, initialPosition);
3660         if(startedFromSetupPosition)
3661             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3662     }
3663
3664     /* [HGM] Set castling rights. Take the outermost Rooks,
3665        to make it also work for FRC opening positions. Note that board12
3666        is really defective for later FRC positions, as it has no way to
3667        indicate which Rook can castle if they are on the same side of King.
3668        For the initial position we grant rights to the outermost Rooks,
3669        and remember thos rights, and we then copy them on positions
3670        later in an FRC game. This means WB might not recognize castlings with
3671        Rooks that have moved back to their original position as illegal,
3672        but in ICS mode that is not its job anyway.
3673     */
3674     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3675     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3676
3677         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3678             if(board[0][i] == WhiteRook) j = i;
3679         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3680         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3681             if(board[0][i] == WhiteRook) j = i;
3682         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3683         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3684             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3685         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3686         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3687             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3688         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3689
3690         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3692             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694             if(board[BOARD_HEIGHT-1][k] == bKing)
3695                 initialRights[5] = castlingRights[moveNum][5] = k;
3696     } else { int r;
3697         r = castlingRights[moveNum][0] = initialRights[0];
3698         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3699         r = castlingRights[moveNum][1] = initialRights[1];
3700         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3701         r = castlingRights[moveNum][3] = initialRights[3];
3702         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3703         r = castlingRights[moveNum][4] = initialRights[4];
3704         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3705         /* wildcastle kludge: always assume King has rights */
3706         r = castlingRights[moveNum][2] = initialRights[2];
3707         r = castlingRights[moveNum][5] = initialRights[5];
3708     }
3709     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3710     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3711
3712     
3713     if (ics_getting_history == H_GOT_REQ_HEADER ||
3714         ics_getting_history == H_GOT_UNREQ_HEADER) {
3715         /* This was an initial position from a move list, not
3716            the current position */
3717         return;
3718     }
3719     
3720     /* Update currentMove and known move number limits */
3721     newMove = newGame || moveNum > forwardMostMove;
3722
3723     if (newGame) {
3724         forwardMostMove = backwardMostMove = currentMove = moveNum;
3725         if (gameMode == IcsExamining && moveNum == 0) {
3726           /* Workaround for ICS limitation: we are not told the wild
3727              type when starting to examine a game.  But if we ask for
3728              the move list, the move list header will tell us */
3729             ics_getting_history = H_REQUESTED;
3730             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3731             SendToICS(str);
3732         }
3733     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3734                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3735 #if ZIPPY
3736         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3737         /* [HGM] applied this also to an engine that is silently watching        */
3738         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3739             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3740             gameInfo.variant == currentlyInitializedVariant) {
3741           takeback = forwardMostMove - moveNum;
3742           for (i = 0; i < takeback; i++) {
3743             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3744             SendToProgram("undo\n", &first);
3745           }
3746         }
3747 #endif
3748
3749         forwardMostMove = moveNum;
3750         if (!pausing || currentMove > forwardMostMove)
3751           currentMove = forwardMostMove;
3752     } else {
3753         /* New part of history that is not contiguous with old part */ 
3754         if (pausing && gameMode == IcsExamining) {
3755             pauseExamInvalid = TRUE;
3756             forwardMostMove = pauseExamForwardMostMove;
3757             return;
3758         }
3759         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3760 #if ZIPPY
3761             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3762                 // [HGM] when we will receive the move list we now request, it will be
3763                 // fed to the engine from the first move on. So if the engine is not
3764                 // in the initial position now, bring it there.
3765                 InitChessProgram(&first, 0);
3766             }
3767 #endif
3768             ics_getting_history = H_REQUESTED;
3769             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3770             SendToICS(str);
3771         }
3772         forwardMostMove = backwardMostMove = currentMove = moveNum;
3773     }
3774     
3775     /* Update the clocks */
3776     if (strchr(elapsed_time, '.')) {
3777       /* Time is in ms */
3778       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3779       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3780     } else {
3781       /* Time is in seconds */
3782       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3783       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3784     }
3785       
3786
3787 #if ZIPPY
3788     if (appData.zippyPlay && newGame &&
3789         gameMode != IcsObserving && gameMode != IcsIdle &&
3790         gameMode != IcsExamining)
3791       ZippyFirstBoard(moveNum, basetime, increment);
3792 #endif
3793     
3794     /* Put the move on the move list, first converting
3795        to canonical algebraic form. */
3796     if (moveNum > 0) {
3797   if (appData.debugMode) {
3798     if (appData.debugMode) { int f = forwardMostMove;
3799         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3800                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3801     }
3802     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3803     fprintf(debugFP, "moveNum = %d\n", moveNum);
3804     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3805     setbuf(debugFP, NULL);
3806   }
3807         if (moveNum <= backwardMostMove) {
3808             /* We don't know what the board looked like before
3809                this move.  Punt. */
3810             strcpy(parseList[moveNum - 1], move_str);
3811             strcat(parseList[moveNum - 1], " ");
3812             strcat(parseList[moveNum - 1], elapsed_time);
3813             moveList[moveNum - 1][0] = NULLCHAR;
3814         } else if (strcmp(move_str, "none") == 0) {
3815             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3816             /* Again, we don't know what the board looked like;
3817                this is really the start of the game. */
3818             parseList[moveNum - 1][0] = NULLCHAR;
3819             moveList[moveNum - 1][0] = NULLCHAR;
3820             backwardMostMove = moveNum;
3821             startedFromSetupPosition = TRUE;
3822             fromX = fromY = toX = toY = -1;
3823         } else {
3824           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3825           //                 So we parse the long-algebraic move string in stead of the SAN move
3826           int valid; char buf[MSG_SIZ], *prom;
3827
3828           // str looks something like "Q/a1-a2"; kill the slash
3829           if(str[1] == '/') 
3830                 sprintf(buf, "%c%s", str[0], str+2);
3831           else  strcpy(buf, str); // might be castling
3832           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3833                 strcat(buf, prom); // long move lacks promo specification!
3834           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3835                 if(appData.debugMode) 
3836                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3837                 strcpy(move_str, buf);
3838           }
3839           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3840                                 &fromX, &fromY, &toX, &toY, &promoChar)
3841                || ParseOneMove(buf, moveNum - 1, &moveType,
3842                                 &fromX, &fromY, &toX, &toY, &promoChar);
3843           // end of long SAN patch
3844           if (valid) {
3845             (void) CoordsToAlgebraic(boards[moveNum - 1],
3846                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3847                                      fromY, fromX, toY, toX, promoChar,
3848                                      parseList[moveNum-1]);
3849             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3850                              castlingRights[moveNum]) ) {
3851               case MT_NONE:
3852               case MT_STALEMATE:
3853               default:
3854                 break;
3855               case MT_CHECK:
3856                 if(gameInfo.variant != VariantShogi)
3857                     strcat(parseList[moveNum - 1], "+");
3858                 break;
3859               case MT_CHECKMATE:
3860               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3861                 strcat(parseList[moveNum - 1], "#");
3862                 break;
3863             }
3864             strcat(parseList[moveNum - 1], " ");
3865             strcat(parseList[moveNum - 1], elapsed_time);
3866             /* currentMoveString is set as a side-effect of ParseOneMove */
3867             strcpy(moveList[moveNum - 1], currentMoveString);
3868             strcat(moveList[moveNum - 1], "\n");
3869           } else {
3870             /* Move from ICS was illegal!?  Punt. */
3871   if (appData.debugMode) {
3872     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3873     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3874   }
3875             strcpy(parseList[moveNum - 1], move_str);
3876             strcat(parseList[moveNum - 1], " ");
3877             strcat(parseList[moveNum - 1], elapsed_time);
3878             moveList[moveNum - 1][0] = NULLCHAR;
3879             fromX = fromY = toX = toY = -1;
3880           }
3881         }
3882   if (appData.debugMode) {
3883     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3884     setbuf(debugFP, NULL);
3885   }
3886
3887 #if ZIPPY
3888         /* Send move to chess program (BEFORE animating it). */
3889         if (appData.zippyPlay && !newGame && newMove && 
3890            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3891
3892             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3893                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3894                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3895                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3896                             move_str);
3897                     DisplayError(str, 0);
3898                 } else {
3899                     if (first.sendTime) {
3900                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3901                     }
3902                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3903                     if (firstMove && !bookHit) {
3904                         firstMove = FALSE;
3905                         if (first.useColors) {
3906                           SendToProgram(gameMode == IcsPlayingWhite ?
3907                                         "white\ngo\n" :
3908                                         "black\ngo\n", &first);
3909                         } else {
3910                           SendToProgram("go\n", &first);
3911                         }
3912                         first.maybeThinking = TRUE;
3913                     }
3914                 }
3915             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3916               if (moveList[moveNum - 1][0] == NULLCHAR) {
3917                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3918                 DisplayError(str, 0);
3919               } else {
3920                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3921                 SendMoveToProgram(moveNum - 1, &first);
3922               }
3923             }
3924         }
3925 #endif
3926     }
3927
3928     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3929         /* If move comes from a remote source, animate it.  If it
3930            isn't remote, it will have already been animated. */
3931         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3932             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3933         }
3934         if (!pausing && appData.highlightLastMove) {
3935             SetHighlights(fromX, fromY, toX, toY);
3936         }
3937     }
3938     
3939     /* Start the clocks */
3940     whiteFlag = blackFlag = FALSE;
3941     appData.clockMode = !(basetime == 0 && increment == 0);
3942     if (ticking == 0) {
3943       ics_clock_paused = TRUE;
3944       StopClocks();
3945     } else if (ticking == 1) {
3946       ics_clock_paused = FALSE;
3947     }
3948     if (gameMode == IcsIdle ||
3949         relation == RELATION_OBSERVING_STATIC ||
3950         relation == RELATION_EXAMINING ||
3951         ics_clock_paused)
3952       DisplayBothClocks();
3953     else
3954       StartClocks();
3955     
3956     /* Display opponents and material strengths */
3957     if (gameInfo.variant != VariantBughouse &&
3958         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3959         if (tinyLayout || smallLayout) {
3960             if(gameInfo.variant == VariantNormal)
3961                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3962                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3963                     basetime, increment);
3964             else
3965                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3966                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3967                     basetime, increment, (int) gameInfo.variant);
3968         } else {
3969             if(gameInfo.variant == VariantNormal)
3970                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3971                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3972                     basetime, increment);
3973             else
3974                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3975                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3976                     basetime, increment, VariantName(gameInfo.variant));
3977         }
3978         DisplayTitle(str);
3979   if (appData.debugMode) {
3980     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3981   }
3982     }
3983
3984    
3985     /* Display the board */
3986     if (!pausing && !appData.noGUI) {
3987       
3988       if (appData.premove)
3989           if (!gotPremove || 
3990              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3991              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3992               ClearPremoveHighlights();
3993
3994       DrawPosition(FALSE, boards[currentMove]);
3995       DisplayMove(moveNum - 1);
3996       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3997             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3998               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3999         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4000       }
4001     }
4002
4003     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4004 #if ZIPPY
4005     if(bookHit) { // [HGM] book: simulate book reply
4006         static char bookMove[MSG_SIZ]; // a bit generous?
4007
4008         programStats.nodes = programStats.depth = programStats.time = 
4009         programStats.score = programStats.got_only_move = 0;
4010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4011
4012         strcpy(bookMove, "move ");
4013         strcat(bookMove, bookHit);
4014         HandleMachineMove(bookMove, &first);
4015     }
4016 #endif
4017 }
4018
4019 void
4020 GetMoveListEvent()
4021 {
4022     char buf[MSG_SIZ];
4023     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4024         ics_getting_history = H_REQUESTED;
4025         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4026         SendToICS(buf);
4027     }
4028 }
4029
4030 void
4031 AnalysisPeriodicEvent(force)
4032      int force;
4033 {
4034     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4035          && !force) || !appData.periodicUpdates)
4036       return;
4037
4038     /* Send . command to Crafty to collect stats */
4039     SendToProgram(".\n", &first);
4040
4041     /* Don't send another until we get a response (this makes
4042        us stop sending to old Crafty's which don't understand
4043        the "." command (sending illegal cmds resets node count & time,
4044        which looks bad)) */
4045     programStats.ok_to_send = 0;
4046 }
4047
4048 void ics_update_width(new_width)
4049         int new_width;
4050 {
4051         ics_printf("set width %d\n", new_width);
4052 }
4053
4054 void
4055 SendMoveToProgram(moveNum, cps)
4056      int moveNum;
4057      ChessProgramState *cps;
4058 {
4059     char buf[MSG_SIZ];
4060
4061     if (cps->useUsermove) {
4062       SendToProgram("usermove ", cps);
4063     }
4064     if (cps->useSAN) {
4065       char *space;
4066       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4067         int len = space - parseList[moveNum];
4068         memcpy(buf, parseList[moveNum], len);
4069         buf[len++] = '\n';
4070         buf[len] = NULLCHAR;
4071       } else {
4072         sprintf(buf, "%s\n", parseList[moveNum]);
4073       }
4074       SendToProgram(buf, cps);
4075     } else {
4076       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4077         AlphaRank(moveList[moveNum], 4);
4078         SendToProgram(moveList[moveNum], cps);
4079         AlphaRank(moveList[moveNum], 4); // and back
4080       } else
4081       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4082        * the engine. It would be nice to have a better way to identify castle 
4083        * moves here. */
4084       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4085                                                                          && cps->useOOCastle) {
4086         int fromX = moveList[moveNum][0] - AAA; 
4087         int fromY = moveList[moveNum][1] - ONE;
4088         int toX = moveList[moveNum][2] - AAA; 
4089         int toY = moveList[moveNum][3] - ONE;
4090         if((boards[moveNum][fromY][fromX] == WhiteKing 
4091             && boards[moveNum][toY][toX] == WhiteRook)
4092            || (boards[moveNum][fromY][fromX] == BlackKing 
4093                && boards[moveNum][toY][toX] == BlackRook)) {
4094           if(toX > fromX) SendToProgram("O-O\n", cps);
4095           else SendToProgram("O-O-O\n", cps);
4096         }
4097         else SendToProgram(moveList[moveNum], cps);
4098       }
4099       else SendToProgram(moveList[moveNum], cps);
4100       /* End of additions by Tord */
4101     }
4102
4103     /* [HGM] setting up the opening has brought engine in force mode! */
4104     /*       Send 'go' if we are in a mode where machine should play. */
4105     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4106         (gameMode == TwoMachinesPlay   ||
4107 #ifdef ZIPPY
4108          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4109 #endif
4110          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4111         SendToProgram("go\n", cps);
4112   if (appData.debugMode) {
4113     fprintf(debugFP, "(extra)\n");
4114   }
4115     }
4116     setboardSpoiledMachineBlack = 0;
4117 }
4118
4119 void
4120 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4121      ChessMove moveType;
4122      int fromX, fromY, toX, toY;
4123 {
4124     char user_move[MSG_SIZ];
4125
4126     switch (moveType) {
4127       default:
4128         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4129                 (int)moveType, fromX, fromY, toX, toY);
4130         DisplayError(user_move + strlen("say "), 0);
4131         break;
4132       case WhiteKingSideCastle:
4133       case BlackKingSideCastle:
4134       case WhiteQueenSideCastleWild:
4135       case BlackQueenSideCastleWild:
4136       /* PUSH Fabien */
4137       case WhiteHSideCastleFR:
4138       case BlackHSideCastleFR:
4139       /* POP Fabien */
4140         sprintf(user_move, "o-o\n");
4141         break;
4142       case WhiteQueenSideCastle:
4143       case BlackQueenSideCastle:
4144       case WhiteKingSideCastleWild:
4145       case BlackKingSideCastleWild:
4146       /* PUSH Fabien */
4147       case WhiteASideCastleFR:
4148       case BlackASideCastleFR:
4149       /* POP Fabien */
4150         sprintf(user_move, "o-o-o\n");
4151         break;
4152       case WhitePromotionQueen:
4153       case BlackPromotionQueen:
4154       case WhitePromotionRook:
4155       case BlackPromotionRook:
4156       case WhitePromotionBishop:
4157       case BlackPromotionBishop:
4158       case WhitePromotionKnight:
4159       case BlackPromotionKnight:
4160       case WhitePromotionKing:
4161       case BlackPromotionKing:
4162       case WhitePromotionChancellor:
4163       case BlackPromotionChancellor:
4164       case WhitePromotionArchbishop:
4165       case BlackPromotionArchbishop:
4166         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4167             sprintf(user_move, "%c%c%c%c=%c\n",
4168                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4169                 PieceToChar(WhiteFerz));
4170         else if(gameInfo.variant == VariantGreat)
4171             sprintf(user_move, "%c%c%c%c=%c\n",
4172                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173                 PieceToChar(WhiteMan));
4174         else
4175             sprintf(user_move, "%c%c%c%c=%c\n",
4176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177                 PieceToChar(PromoPiece(moveType)));
4178         break;
4179       case WhiteDrop:
4180       case BlackDrop:
4181         sprintf(user_move, "%c@%c%c\n",
4182                 ToUpper(PieceToChar((ChessSquare) fromX)),
4183                 AAA + toX, ONE + toY);
4184         break;
4185       case NormalMove:
4186       case WhiteCapturesEnPassant:
4187       case BlackCapturesEnPassant:
4188       case IllegalMove:  /* could be a variant we don't quite understand */
4189         sprintf(user_move, "%c%c%c%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4191         break;
4192     }
4193     SendToICS(user_move);
4194     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4195         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4196 }
4197
4198 void
4199 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4200      int rf, ff, rt, ft;
4201      char promoChar;
4202      char move[7];
4203 {
4204     if (rf == DROP_RANK) {
4205         sprintf(move, "%c@%c%c\n",
4206                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4207     } else {
4208         if (promoChar == 'x' || promoChar == NULLCHAR) {
4209             sprintf(move, "%c%c%c%c\n",
4210                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4211         } else {
4212             sprintf(move, "%c%c%c%c%c\n",
4213                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4214         }
4215     }
4216 }
4217
4218 void
4219 ProcessICSInitScript(f)
4220      FILE *f;
4221 {
4222     char buf[MSG_SIZ];
4223
4224     while (fgets(buf, MSG_SIZ, f)) {
4225         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4226     }
4227
4228     fclose(f);
4229 }
4230
4231
4232 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4233 void
4234 AlphaRank(char *move, int n)
4235 {
4236 //    char *p = move, c; int x, y;
4237
4238     if (appData.debugMode) {
4239         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4240     }
4241
4242     if(move[1]=='*' && 
4243        move[2]>='0' && move[2]<='9' &&
4244        move[3]>='a' && move[3]<='x'    ) {
4245         move[1] = '@';
4246         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4247         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4248     } else
4249     if(move[0]>='0' && move[0]<='9' &&
4250        move[1]>='a' && move[1]<='x' &&
4251        move[2]>='0' && move[2]<='9' &&
4252        move[3]>='a' && move[3]<='x'    ) {
4253         /* input move, Shogi -> normal */
4254         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4255         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4256         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4257         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4258     } else
4259     if(move[1]=='@' &&
4260        move[3]>='0' && move[3]<='9' &&
4261        move[2]>='a' && move[2]<='x'    ) {
4262         move[1] = '*';
4263         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4264         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4265     } else
4266     if(
4267        move[0]>='a' && move[0]<='x' &&
4268        move[3]>='0' && move[3]<='9' &&
4269        move[2]>='a' && move[2]<='x'    ) {
4270          /* output move, normal -> Shogi */
4271         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4272         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4273         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4274         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4275         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4276     }
4277     if (appData.debugMode) {
4278         fprintf(debugFP, "   out = '%s'\n", move);
4279     }
4280 }
4281
4282 /* Parser for moves from gnuchess, ICS, or user typein box */
4283 Boolean
4284 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4285      char *move;
4286      int moveNum;
4287      ChessMove *moveType;
4288      int *fromX, *fromY, *toX, *toY;
4289      char *promoChar;
4290 {       
4291     if (appData.debugMode) {
4292         fprintf(debugFP, "move to parse: %s\n", move);
4293     }
4294     *moveType = yylexstr(moveNum, move);
4295
4296     switch (*moveType) {
4297       case WhitePromotionChancellor:
4298       case BlackPromotionChancellor:
4299       case WhitePromotionArchbishop:
4300       case BlackPromotionArchbishop:
4301       case WhitePromotionQueen:
4302       case BlackPromotionQueen:
4303       case WhitePromotionRook:
4304       case BlackPromotionRook:
4305       case WhitePromotionBishop:
4306       case BlackPromotionBishop:
4307       case WhitePromotionKnight:
4308       case BlackPromotionKnight:
4309       case WhitePromotionKing:
4310       case BlackPromotionKing:
4311       case NormalMove:
4312       case WhiteCapturesEnPassant:
4313       case BlackCapturesEnPassant:
4314       case WhiteKingSideCastle:
4315       case WhiteQueenSideCastle:
4316       case BlackKingSideCastle:
4317       case BlackQueenSideCastle:
4318       case WhiteKingSideCastleWild:
4319       case WhiteQueenSideCastleWild:
4320       case BlackKingSideCastleWild:
4321       case BlackQueenSideCastleWild:
4322       /* Code added by Tord: */
4323       case WhiteHSideCastleFR:
4324       case WhiteASideCastleFR:
4325       case BlackHSideCastleFR:
4326       case BlackASideCastleFR:
4327       /* End of code added by Tord */
4328       case IllegalMove:         /* bug or odd chess variant */
4329         *fromX = currentMoveString[0] - AAA;
4330         *fromY = currentMoveString[1] - ONE;
4331         *toX = currentMoveString[2] - AAA;
4332         *toY = currentMoveString[3] - ONE;
4333         *promoChar = currentMoveString[4];
4334         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4335             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4336     if (appData.debugMode) {
4337         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4338     }
4339             *fromX = *fromY = *toX = *toY = 0;
4340             return FALSE;
4341         }
4342         if (appData.testLegality) {
4343           return (*moveType != IllegalMove);
4344         } else {
4345           return !(fromX == fromY && toX == toY);
4346         }
4347
4348       case WhiteDrop:
4349       case BlackDrop:
4350         *fromX = *moveType == WhiteDrop ?
4351           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4352           (int) CharToPiece(ToLower(currentMoveString[0]));
4353         *fromY = DROP_RANK;
4354         *toX = currentMoveString[2] - AAA;
4355         *toY = currentMoveString[3] - ONE;
4356         *promoChar = NULLCHAR;
4357         return TRUE;
4358
4359       case AmbiguousMove:
4360       case ImpossibleMove:
4361       case (ChessMove) 0:       /* end of file */
4362       case ElapsedTime:
4363       case Comment:
4364       case PGNTag:
4365       case NAG:
4366       case WhiteWins:
4367       case BlackWins:
4368       case GameIsDrawn:
4369       default:
4370     if (appData.debugMode) {
4371         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4372     }
4373         /* bug? */
4374         *fromX = *fromY = *toX = *toY = 0;
4375         *promoChar = NULLCHAR;
4376         return FALSE;
4377     }
4378 }
4379
4380 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4381 // All positions will have equal probability, but the current method will not provide a unique
4382 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4383 #define DARK 1
4384 #define LITE 2
4385 #define ANY 3
4386
4387 int squaresLeft[4];
4388 int piecesLeft[(int)BlackPawn];
4389 int seed, nrOfShuffles;
4390
4391 void GetPositionNumber()
4392 {       // sets global variable seed
4393         int i;
4394
4395         seed = appData.defaultFrcPosition;
4396         if(seed < 0) { // randomize based on time for negative FRC position numbers
4397                 for(i=0; i<50; i++) seed += random();
4398                 seed = random() ^ random() >> 8 ^ random() << 8;
4399                 if(seed<0) seed = -seed;
4400         }
4401 }
4402
4403 int put(Board board, int pieceType, int rank, int n, int shade)
4404 // put the piece on the (n-1)-th empty squares of the given shade
4405 {
4406         int i;
4407
4408         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4409                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4410                         board[rank][i] = (ChessSquare) pieceType;
4411                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4412                         squaresLeft[ANY]--;
4413                         piecesLeft[pieceType]--; 
4414                         return i;
4415                 }
4416         }
4417         return -1;
4418 }
4419
4420
4421 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4422 // calculate where the next piece goes, (any empty square), and put it there
4423 {
4424         int i;
4425
4426         i = seed % squaresLeft[shade];
4427         nrOfShuffles *= squaresLeft[shade];
4428         seed /= squaresLeft[shade];
4429         put(board, pieceType, rank, i, shade);
4430 }
4431
4432 void AddTwoPieces(Board board, int pieceType, int rank)
4433 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4434 {
4435         int i, n=squaresLeft[ANY], j=n-1, k;
4436
4437         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4438         i = seed % k;  // pick one
4439         nrOfShuffles *= k;
4440         seed /= k;
4441         while(i >= j) i -= j--;
4442         j = n - 1 - j; i += j;
4443         put(board, pieceType, rank, j, ANY);
4444         put(board, pieceType, rank, i, ANY);
4445 }
4446
4447 void SetUpShuffle(Board board, int number)
4448 {
4449         int i, p, first=1;
4450
4451         GetPositionNumber(); nrOfShuffles = 1;
4452
4453         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4454         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4455         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4456
4457         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4458
4459         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4460             p = (int) board[0][i];
4461             if(p < (int) BlackPawn) piecesLeft[p] ++;
4462             board[0][i] = EmptySquare;
4463         }
4464
4465         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4466             // shuffles restricted to allow normal castling put KRR first
4467             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4468                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4469             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4470                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4471             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4472                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4473             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4474                 put(board, WhiteRook, 0, 0, ANY);
4475             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4476         }
4477
4478         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4479             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4480             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4481                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4482                 while(piecesLeft[p] >= 2) {
4483                     AddOnePiece(board, p, 0, LITE);
4484                     AddOnePiece(board, p, 0, DARK);
4485                 }
4486                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4487             }
4488
4489         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4490             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4491             // but we leave King and Rooks for last, to possibly obey FRC restriction
4492             if(p == (int)WhiteRook) continue;
4493             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4494             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4495         }
4496
4497         // now everything is placed, except perhaps King (Unicorn) and Rooks
4498
4499         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4500             // Last King gets castling rights
4501             while(piecesLeft[(int)WhiteUnicorn]) {
4502                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4503                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4504             }
4505
4506             while(piecesLeft[(int)WhiteKing]) {
4507                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4508                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4509             }
4510
4511
4512         } else {
4513             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4514             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4515         }
4516
4517         // Only Rooks can be left; simply place them all
4518         while(piecesLeft[(int)WhiteRook]) {
4519                 i = put(board, WhiteRook, 0, 0, ANY);
4520                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4521                         if(first) {
4522                                 first=0;
4523                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4524                         }
4525                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4526                 }
4527         }
4528         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4529             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4530         }
4531
4532         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4533 }
4534
4535 int SetCharTable( char *table, const char * map )
4536 /* [HGM] moved here from winboard.c because of its general usefulness */
4537 /*       Basically a safe strcpy that uses the last character as King */
4538 {
4539     int result = FALSE; int NrPieces;
4540
4541     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4542                     && NrPieces >= 12 && !(NrPieces&1)) {
4543         int i; /* [HGM] Accept even length from 12 to 34 */
4544
4545         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4546         for( i=0; i<NrPieces/2-1; i++ ) {
4547             table[i] = map[i];
4548             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4549         }
4550         table[(int) WhiteKing]  = map[NrPieces/2-1];
4551         table[(int) BlackKing]  = map[NrPieces-1];
4552
4553         result = TRUE;
4554     }
4555
4556     return result;
4557 }
4558
4559 void Prelude(Board board)
4560 {       // [HGM] superchess: random selection of exo-pieces
4561         int i, j, k; ChessSquare p; 
4562         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4563
4564         GetPositionNumber(); // use FRC position number
4565
4566         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4567             SetCharTable(pieceToChar, appData.pieceToCharTable);
4568             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4569                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4570         }
4571
4572         j = seed%4;                 seed /= 4; 
4573         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4574         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4575         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4576         j = seed%3 + (seed%3 >= j); seed /= 3; 
4577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4580         j = seed%3;                 seed /= 3; 
4581         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4584         j = seed%2 + (seed%2 >= j); seed /= 2; 
4585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4588         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4589         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4590         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4591         put(board, exoPieces[0],    0, 0, ANY);
4592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4593 }
4594
4595 void
4596 InitPosition(redraw)
4597      int redraw;
4598 {
4599     ChessSquare (* pieces)[BOARD_SIZE];
4600     int i, j, pawnRow, overrule,
4601     oldx = gameInfo.boardWidth,
4602     oldy = gameInfo.boardHeight,
4603     oldh = gameInfo.holdingsWidth,
4604     oldv = gameInfo.variant;
4605
4606     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4607
4608     /* [AS] Initialize pv info list [HGM] and game status */
4609     {
4610         for( i=0; i<MAX_MOVES; i++ ) {
4611             pvInfoList[i].depth = 0;
4612             epStatus[i]=EP_NONE;
4613             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4614         }
4615
4616         initialRulePlies = 0; /* 50-move counter start */
4617
4618         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4619         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4620     }
4621
4622     
4623     /* [HGM] logic here is completely changed. In stead of full positions */
4624     /* the initialized data only consist of the two backranks. The switch */
4625     /* selects which one we will use, which is than copied to the Board   */
4626     /* initialPosition, which for the rest is initialized by Pawns and    */
4627     /* empty squares. This initial position is then copied to boards[0],  */
4628     /* possibly after shuffling, so that it remains available.            */
4629
4630     gameInfo.holdingsWidth = 0; /* default board sizes */
4631     gameInfo.boardWidth    = 8;
4632     gameInfo.boardHeight   = 8;
4633     gameInfo.holdingsSize  = 0;
4634     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4635     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4636     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4637
4638     switch (gameInfo.variant) {
4639     case VariantFischeRandom:
4640       shuffleOpenings = TRUE;
4641     default:
4642       pieces = FIDEArray;
4643       break;
4644     case VariantShatranj:
4645       pieces = ShatranjArray;
4646       nrCastlingRights = 0;
4647       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4648       break;
4649     case VariantTwoKings:
4650       pieces = twoKingsArray;
4651       break;
4652     case VariantCapaRandom:
4653       shuffleOpenings = TRUE;
4654     case VariantCapablanca:
4655       pieces = CapablancaArray;
4656       gameInfo.boardWidth = 10;
4657       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4658       break;
4659     case VariantGothic:
4660       pieces = GothicArray;
4661       gameInfo.boardWidth = 10;
4662       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4663       break;
4664     case VariantJanus:
4665       pieces = JanusArray;
4666       gameInfo.boardWidth = 10;
4667       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4668       nrCastlingRights = 6;
4669         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4670         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4671         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4672         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4673         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4674         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4675       break;
4676     case VariantFalcon:
4677       pieces = FalconArray;
4678       gameInfo.boardWidth = 10;
4679       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4680       break;
4681     case VariantXiangqi:
4682       pieces = XiangqiArray;
4683       gameInfo.boardWidth  = 9;
4684       gameInfo.boardHeight = 10;
4685       nrCastlingRights = 0;
4686       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4687       break;
4688     case VariantShogi:
4689       pieces = ShogiArray;
4690       gameInfo.boardWidth  = 9;
4691       gameInfo.boardHeight = 9;
4692       gameInfo.holdingsSize = 7;
4693       nrCastlingRights = 0;
4694       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4695       break;
4696     case VariantCourier:
4697       pieces = CourierArray;
4698       gameInfo.boardWidth  = 12;
4699       nrCastlingRights = 0;
4700       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4701       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4702       break;
4703     case VariantKnightmate:
4704       pieces = KnightmateArray;
4705       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4706       break;
4707     case VariantFairy:
4708       pieces = fairyArray;
4709       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4710       break;
4711     case VariantGreat:
4712       pieces = GreatArray;
4713       gameInfo.boardWidth = 10;
4714       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4715       gameInfo.holdingsSize = 8;
4716       break;
4717     case VariantSuper:
4718       pieces = FIDEArray;
4719       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4720       gameInfo.holdingsSize = 8;
4721       startedFromSetupPosition = TRUE;
4722       break;
4723     case VariantCrazyhouse:
4724     case VariantBughouse:
4725       pieces = FIDEArray;
4726       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4727       gameInfo.holdingsSize = 5;
4728       break;
4729     case VariantWildCastle:
4730       pieces = FIDEArray;
4731       /* !!?shuffle with kings guaranteed to be on d or e file */
4732       shuffleOpenings = 1;
4733       break;
4734     case VariantNoCastle:
4735       pieces = FIDEArray;
4736       nrCastlingRights = 0;
4737       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4738       /* !!?unconstrained back-rank shuffle */
4739       shuffleOpenings = 1;
4740       break;
4741     }
4742
4743     overrule = 0;
4744     if(appData.NrFiles >= 0) {
4745         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4746         gameInfo.boardWidth = appData.NrFiles;
4747     }
4748     if(appData.NrRanks >= 0) {
4749         gameInfo.boardHeight = appData.NrRanks;
4750     }
4751     if(appData.holdingsSize >= 0) {
4752         i = appData.holdingsSize;
4753         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4754         gameInfo.holdingsSize = i;
4755     }
4756     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4757     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4758         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4759
4760     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4761     if(pawnRow < 1) pawnRow = 1;
4762
4763     /* User pieceToChar list overrules defaults */
4764     if(appData.pieceToCharTable != NULL)
4765         SetCharTable(pieceToChar, appData.pieceToCharTable);
4766
4767     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4768
4769         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4770             s = (ChessSquare) 0; /* account holding counts in guard band */
4771         for( i=0; i<BOARD_HEIGHT; i++ )
4772             initialPosition[i][j] = s;
4773
4774         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4775         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4776         initialPosition[pawnRow][j] = WhitePawn;
4777         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4778         if(gameInfo.variant == VariantXiangqi) {
4779             if(j&1) {
4780                 initialPosition[pawnRow][j] = 
4781                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4782                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4783                    initialPosition[2][j] = WhiteCannon;
4784                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4785                 }
4786             }
4787         }
4788         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4789     }
4790     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4791
4792             j=BOARD_LEFT+1;
4793             initialPosition[1][j] = WhiteBishop;
4794             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4795             j=BOARD_RGHT-2;
4796             initialPosition[1][j] = WhiteRook;
4797             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4798     }
4799
4800     if( nrCastlingRights == -1) {
4801         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4802         /*       This sets default castling rights from none to normal corners   */
4803         /* Variants with other castling rights must set them themselves above    */
4804         nrCastlingRights = 6;
4805        
4806         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4807         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4808         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4809         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4810         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4811         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4812      }
4813
4814      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4815      if(gameInfo.variant == VariantGreat) { // promotion commoners
4816         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4817         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4818         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4819         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4820      }
4821   if (appData.debugMode) {
4822     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4823   }
4824     if(shuffleOpenings) {
4825         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4826         startedFromSetupPosition = TRUE;
4827     }
4828     if(startedFromPositionFile) {
4829       /* [HGM] loadPos: use PositionFile for every new game */
4830       CopyBoard(initialPosition, filePosition);
4831       for(i=0; i<nrCastlingRights; i++)
4832           castlingRights[0][i] = initialRights[i] = fileRights[i];
4833       startedFromSetupPosition = TRUE;
4834     }
4835
4836     CopyBoard(boards[0], initialPosition);
4837
4838     if(oldx != gameInfo.boardWidth ||
4839        oldy != gameInfo.boardHeight ||
4840        oldh != gameInfo.holdingsWidth
4841 #ifdef GOTHIC
4842        || oldv == VariantGothic ||        // For licensing popups
4843        gameInfo.variant == VariantGothic
4844 #endif
4845 #ifdef FALCON
4846        || oldv == VariantFalcon ||
4847        gameInfo.variant == VariantFalcon
4848 #endif
4849                                          )
4850             InitDrawingSizes(-2 ,0);
4851
4852     if (redraw)
4853       DrawPosition(TRUE, boards[currentMove]);
4854 }
4855
4856 void
4857 SendBoard(cps, moveNum)
4858      ChessProgramState *cps;
4859      int moveNum;
4860 {
4861     char message[MSG_SIZ];
4862     
4863     if (cps->useSetboard) {
4864       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4865       sprintf(message, "setboard %s\n", fen);
4866       SendToProgram(message, cps);
4867       free(fen);
4868
4869     } else {
4870       ChessSquare *bp;
4871       int i, j;
4872       /* Kludge to set black to move, avoiding the troublesome and now
4873        * deprecated "black" command.
4874        */
4875       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4876
4877       SendToProgram("edit\n", cps);
4878       SendToProgram("#\n", cps);
4879       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4880         bp = &boards[moveNum][i][BOARD_LEFT];
4881         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4882           if ((int) *bp < (int) BlackPawn) {
4883             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4884                     AAA + j, ONE + i);
4885             if(message[0] == '+' || message[0] == '~') {
4886                 sprintf(message, "%c%c%c+\n",
4887                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4888                         AAA + j, ONE + i);
4889             }
4890             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4891                 message[1] = BOARD_RGHT   - 1 - j + '1';
4892                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4893             }
4894             SendToProgram(message, cps);
4895           }
4896         }
4897       }
4898     
4899       SendToProgram("c\n", cps);
4900       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4901         bp = &boards[moveNum][i][BOARD_LEFT];
4902         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4903           if (((int) *bp != (int) EmptySquare)
4904               && ((int) *bp >= (int) BlackPawn)) {
4905             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4906                     AAA + j, ONE + i);
4907             if(message[0] == '+' || message[0] == '~') {
4908                 sprintf(message, "%c%c%c+\n",
4909                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4910                         AAA + j, ONE + i);
4911             }
4912             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4913                 message[1] = BOARD_RGHT   - 1 - j + '1';
4914                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4915             }
4916             SendToProgram(message, cps);
4917           }
4918         }
4919       }
4920     
4921       SendToProgram(".\n", cps);
4922     }
4923     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4924 }
4925
4926 int
4927 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4928 {
4929     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4930     /* [HGM] add Shogi promotions */
4931     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4932     ChessSquare piece;
4933     ChessMove moveType;
4934     Boolean premove;
4935
4936     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4937     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4938
4939     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4940       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4941         return FALSE;
4942
4943     piece = boards[currentMove][fromY][fromX];
4944     if(gameInfo.variant == VariantShogi) {
4945         promotionZoneSize = 3;
4946         highestPromotingPiece = (int)WhiteFerz;
4947     }
4948
4949     // next weed out all moves that do not touch the promotion zone at all
4950     if((int)piece >= BlackPawn) {
4951         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4952              return FALSE;
4953         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4954     } else {
4955         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4956            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4957     }
4958
4959     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4960
4961     // weed out mandatory Shogi promotions
4962     if(gameInfo.variant == VariantShogi) {
4963         if(piece >= BlackPawn) {
4964             if(toY == 0 && piece == BlackPawn ||
4965                toY == 0 && piece == BlackQueen ||
4966                toY <= 1 && piece == BlackKnight) {
4967                 *promoChoice = '+';
4968                 return FALSE;
4969             }
4970         } else {
4971             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4972                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4973                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4974                 *promoChoice = '+';
4975                 return FALSE;
4976             }
4977         }
4978     }
4979
4980     // weed out obviously illegal Pawn moves
4981     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4982         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4983         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4984         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4985         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4986         // note we are not allowed to test for valid (non-)capture, due to premove
4987     }
4988
4989     // we either have a choice what to promote to, or (in Shogi) whether to promote
4990     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4991         *promoChoice = PieceToChar(BlackFerz);  // no choice
4992         return FALSE;
4993     }
4994     if(appData.alwaysPromoteToQueen) { // predetermined
4995         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4996              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4997         else *promoChoice = PieceToChar(BlackQueen);
4998         return FALSE;
4999     }
5000
5001     // suppress promotion popup on illegal moves that are not premoves
5002     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5003               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5004     if(appData.testLegality && !premove) {
5005         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5006                         epStatus[currentMove], castlingRights[currentMove],
5007                         fromY, fromX, toY, toX, NULLCHAR);
5008         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5009            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5010             return FALSE;
5011     }
5012
5013     return TRUE;
5014 }
5015
5016 int
5017 InPalace(row, column)
5018      int row, column;
5019 {   /* [HGM] for Xiangqi */
5020     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5021          column < (BOARD_WIDTH + 4)/2 &&
5022          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5023     return FALSE;
5024 }
5025
5026 int
5027 PieceForSquare (x, y)
5028      int x;
5029      int y;
5030 {
5031   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5032      return -1;
5033   else
5034      return boards[currentMove][y][x];
5035 }
5036
5037 int
5038 OKToStartUserMove(x, y)
5039      int x, y;
5040 {
5041     ChessSquare from_piece;
5042     int white_piece;
5043
5044     if (matchMode) return FALSE;
5045     if (gameMode == EditPosition) return TRUE;
5046
5047     if (x >= 0 && y >= 0)
5048       from_piece = boards[currentMove][y][x];
5049     else
5050       from_piece = EmptySquare;
5051
5052     if (from_piece == EmptySquare) return FALSE;
5053
5054     white_piece = (int)from_piece >= (int)WhitePawn &&
5055       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5056
5057     switch (gameMode) {
5058       case PlayFromGameFile:
5059       case AnalyzeFile:
5060       case TwoMachinesPlay:
5061       case EndOfGame:
5062         return FALSE;
5063
5064       case IcsObserving:
5065       case IcsIdle:
5066         return FALSE;
5067
5068       case MachinePlaysWhite:
5069       case IcsPlayingBlack:
5070         if (appData.zippyPlay) return FALSE;
5071         if (white_piece) {
5072             DisplayMoveError(_("You are playing Black"));
5073             return FALSE;
5074         }
5075         break;
5076
5077       case MachinePlaysBlack:
5078       case IcsPlayingWhite:
5079         if (appData.zippyPlay) return FALSE;
5080         if (!white_piece) {
5081             DisplayMoveError(_("You are playing White"));
5082             return FALSE;
5083         }
5084         break;
5085
5086       case EditGame:
5087         if (!white_piece && WhiteOnMove(currentMove)) {
5088             DisplayMoveError(_("It is White's turn"));
5089             return FALSE;
5090         }           
5091         if (white_piece && !WhiteOnMove(currentMove)) {
5092             DisplayMoveError(_("It is Black's turn"));
5093             return FALSE;
5094         }           
5095         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5096             /* Editing correspondence game history */
5097             /* Could disallow this or prompt for confirmation */
5098             cmailOldMove = -1;
5099         }
5100         if (currentMove < forwardMostMove) {
5101             /* Discarding moves */
5102             /* Could prompt for confirmation here,
5103                but I don't think that's such a good idea */
5104             forwardMostMove = currentMove;
5105         }
5106         break;
5107
5108       case BeginningOfGame:
5109         if (appData.icsActive) return FALSE;
5110         if (!appData.noChessProgram) {
5111             if (!white_piece) {
5112                 DisplayMoveError(_("You are playing White"));
5113                 return FALSE;
5114             }
5115         }
5116         break;
5117         
5118       case Training:
5119         if (!white_piece && WhiteOnMove(currentMove)) {
5120             DisplayMoveError(_("It is White's turn"));
5121             return FALSE;
5122         }           
5123         if (white_piece && !WhiteOnMove(currentMove)) {
5124             DisplayMoveError(_("It is Black's turn"));
5125             return FALSE;
5126         }           
5127         break;
5128
5129       default:
5130       case IcsExamining:
5131         break;
5132     }
5133     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5134         && gameMode != AnalyzeFile && gameMode != Training) {
5135         DisplayMoveError(_("Displayed position is not current"));
5136         return FALSE;
5137     }
5138     return TRUE;
5139 }
5140
5141 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5142 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5143 int lastLoadGameUseList = FALSE;
5144 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5145 ChessMove lastLoadGameStart = (ChessMove) 0;
5146
5147 ChessMove
5148 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5149      int fromX, fromY, toX, toY;
5150      int promoChar;
5151      Boolean captureOwn;
5152 {
5153     ChessMove moveType;
5154     ChessSquare pdown, pup;
5155
5156     /* Check if the user is playing in turn.  This is complicated because we
5157        let the user "pick up" a piece before it is his turn.  So the piece he
5158        tried to pick up may have been captured by the time he puts it down!
5159        Therefore we use the color the user is supposed to be playing in this
5160        test, not the color of the piece that is currently on the starting
5161        square---except in EditGame mode, where the user is playing both
5162        sides; fortunately there the capture race can't happen.  (It can
5163        now happen in IcsExamining mode, but that's just too bad.  The user
5164        will get a somewhat confusing message in that case.)
5165        */
5166
5167     switch (gameMode) {
5168       case PlayFromGameFile:
5169       case AnalyzeFile:
5170       case TwoMachinesPlay:
5171       case EndOfGame:
5172       case IcsObserving:
5173       case IcsIdle:
5174         /* We switched into a game mode where moves are not accepted,
5175            perhaps while the mouse button was down. */
5176         return ImpossibleMove;
5177
5178       case MachinePlaysWhite:
5179         /* User is moving for Black */
5180         if (WhiteOnMove(currentMove)) {
5181             DisplayMoveError(_("It is White's turn"));
5182             return ImpossibleMove;
5183         }
5184         break;
5185
5186       case MachinePlaysBlack:
5187         /* User is moving for White */
5188         if (!WhiteOnMove(currentMove)) {
5189             DisplayMoveError(_("It is Black's turn"));
5190             return ImpossibleMove;
5191         }
5192         break;
5193
5194       case EditGame:
5195       case IcsExamining:
5196       case BeginningOfGame:
5197       case AnalyzeMode:
5198       case Training:
5199         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5200             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5201             /* User is moving for Black */
5202             if (WhiteOnMove(currentMove)) {
5203                 DisplayMoveError(_("It is White's turn"));
5204                 return ImpossibleMove;
5205             }
5206         } else {
5207             /* User is moving for White */
5208             if (!WhiteOnMove(currentMove)) {
5209                 DisplayMoveError(_("It is Black's turn"));
5210                 return ImpossibleMove;
5211             }
5212         }
5213         break;
5214
5215       case IcsPlayingBlack:
5216         /* User is moving for Black */
5217         if (WhiteOnMove(currentMove)) {
5218             if (!appData.premove) {
5219                 DisplayMoveError(_("It is White's turn"));
5220             } else if (toX >= 0 && toY >= 0) {
5221                 premoveToX = toX;
5222                 premoveToY = toY;
5223                 premoveFromX = fromX;
5224                 premoveFromY = fromY;
5225                 premovePromoChar = promoChar;
5226                 gotPremove = 1;
5227                 if (appData.debugMode) 
5228                     fprintf(debugFP, "Got premove: fromX %d,"
5229                             "fromY %d, toX %d, toY %d\n",
5230                             fromX, fromY, toX, toY);
5231             }
5232             return ImpossibleMove;
5233         }
5234         break;
5235
5236       case IcsPlayingWhite:
5237         /* User is moving for White */
5238         if (!WhiteOnMove(currentMove)) {
5239             if (!appData.premove) {
5240                 DisplayMoveError(_("It is Black's turn"));
5241             } else if (toX >= 0 && toY >= 0) {
5242                 premoveToX = toX;
5243                 premoveToY = toY;
5244                 premoveFromX = fromX;
5245                 premoveFromY = fromY;
5246                 premovePromoChar = promoChar;
5247                 gotPremove = 1;
5248                 if (appData.debugMode) 
5249                     fprintf(debugFP, "Got premove: fromX %d,"
5250                             "fromY %d, toX %d, toY %d\n",
5251                             fromX, fromY, toX, toY);
5252             }
5253             return ImpossibleMove;
5254         }
5255         break;
5256
5257       default:
5258         break;
5259
5260       case EditPosition:
5261         /* EditPosition, empty square, or different color piece;
5262            click-click move is possible */
5263         if (toX == -2 || toY == -2) {
5264             boards[0][fromY][fromX] = EmptySquare;
5265             return AmbiguousMove;
5266         } else if (toX >= 0 && toY >= 0) {
5267             boards[0][toY][toX] = boards[0][fromY][fromX];
5268             boards[0][fromY][fromX] = EmptySquare;
5269             return AmbiguousMove;
5270         }
5271         return ImpossibleMove;
5272     }
5273
5274     pdown = boards[currentMove][fromY][fromX];
5275     pup = boards[currentMove][toY][toX];
5276
5277     /* [HGM] If move started in holdings, it means a drop */
5278     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5279          if( pup != EmptySquare ) return ImpossibleMove;
5280          if(appData.testLegality) {
5281              /* it would be more logical if LegalityTest() also figured out
5282               * which drops are legal. For now we forbid pawns on back rank.
5283               * Shogi is on its own here...
5284               */
5285              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5286                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5287                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5288          }
5289          return WhiteDrop; /* Not needed to specify white or black yet */
5290     }
5291
5292     userOfferedDraw = FALSE;
5293         
5294     /* [HGM] always test for legality, to get promotion info */
5295     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5296                           epStatus[currentMove], castlingRights[currentMove],
5297                                          fromY, fromX, toY, toX, promoChar);
5298     /* [HGM] but possibly ignore an IllegalMove result */
5299     if (appData.testLegality) {
5300         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5301             DisplayMoveError(_("Illegal move"));
5302             return ImpossibleMove;
5303         }
5304     }
5305 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5306     return moveType;
5307     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5308        function is made into one that returns an OK move type if FinishMove
5309        should be called. This to give the calling driver routine the
5310        opportunity to finish the userMove input with a promotion popup,
5311        without bothering the user with this for invalid or illegal moves */
5312
5313 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5314 }
5315
5316 /* Common tail of UserMoveEvent and DropMenuEvent */
5317 int
5318 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5319      ChessMove moveType;
5320      int fromX, fromY, toX, toY;
5321      /*char*/int promoChar;
5322 {
5323     char *bookHit = 0;
5324 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5325     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5326         // [HGM] superchess: suppress promotions to non-available piece
5327         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5328         if(WhiteOnMove(currentMove)) {
5329             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5330         } else {
5331             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5332         }
5333     }
5334
5335     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5336        move type in caller when we know the move is a legal promotion */
5337     if(moveType == NormalMove && promoChar)
5338         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5339 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5340     /* [HGM] convert drag-and-drop piece drops to standard form */
5341     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5342          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5343            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5344                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5345 //         fromX = boards[currentMove][fromY][fromX];
5346            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5347            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5348            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5349            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5350          fromY = DROP_RANK;
5351     }
5352
5353     /* [HGM] <popupFix> The following if has been moved here from
5354        UserMoveEvent(). Because it seemed to belon here (why not allow
5355        piece drops in training games?), and because it can only be
5356        performed after it is known to what we promote. */
5357     if (gameMode == Training) {
5358       /* compare the move played on the board to the next move in the
5359        * game. If they match, display the move and the opponent's response. 
5360        * If they don't match, display an error message.
5361        */
5362       int saveAnimate;
5363       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5364       CopyBoard(testBoard, boards[currentMove]);
5365       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5366
5367       if (CompareBoards(testBoard, boards[currentMove+1])) {
5368         ForwardInner(currentMove+1);
5369
5370         /* Autoplay the opponent's response.
5371          * if appData.animate was TRUE when Training mode was entered,
5372          * the response will be animated.
5373          */
5374         saveAnimate = appData.animate;
5375         appData.animate = animateTraining;
5376         ForwardInner(currentMove+1);
5377         appData.animate = saveAnimate;
5378
5379         /* check for the end of the game */
5380         if (currentMove >= forwardMostMove) {
5381           gameMode = PlayFromGameFile;
5382           ModeHighlight();
5383           SetTrainingModeOff();
5384           DisplayInformation(_("End of game"));
5385         }
5386       } else {
5387         DisplayError(_("Incorrect move"), 0);
5388       }
5389       return 1;
5390     }
5391
5392   /* Ok, now we know that the move is good, so we can kill
5393      the previous line in Analysis Mode */
5394   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5395     forwardMostMove = currentMove;
5396   }
5397
5398   /* If we need the chess program but it's dead, restart it */
5399   ResurrectChessProgram();
5400
5401   /* A user move restarts a paused game*/
5402   if (pausing)
5403     PauseEvent();
5404
5405   thinkOutput[0] = NULLCHAR;
5406
5407   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5408
5409   if (gameMode == BeginningOfGame) {
5410     if (appData.noChessProgram) {
5411       gameMode = EditGame;
5412       SetGameInfo();
5413     } else {
5414       char buf[MSG_SIZ];
5415       gameMode = MachinePlaysBlack;
5416       StartClocks();
5417       SetGameInfo();
5418       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5419       DisplayTitle(buf);
5420       if (first.sendName) {
5421         sprintf(buf, "name %s\n", gameInfo.white);
5422         SendToProgram(buf, &first);
5423       }
5424       StartClocks();
5425     }
5426     ModeHighlight();
5427   }
5428 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5429   /* Relay move to ICS or chess engine */
5430   if (appData.icsActive) {
5431     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5432         gameMode == IcsExamining) {
5433       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5434       ics_user_moved = 1;
5435     }
5436   } else {
5437     if (first.sendTime && (gameMode == BeginningOfGame ||
5438                            gameMode == MachinePlaysWhite ||
5439                            gameMode == MachinePlaysBlack)) {
5440       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5441     }
5442     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5443          // [HGM] book: if program might be playing, let it use book
5444         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5445         first.maybeThinking = TRUE;
5446     } else SendMoveToProgram(forwardMostMove-1, &first);
5447     if (currentMove == cmailOldMove + 1) {
5448       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5449     }
5450   }
5451
5452   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5453
5454   switch (gameMode) {
5455   case EditGame:
5456     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5457                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5458     case MT_NONE:
5459     case MT_CHECK:
5460       break;
5461     case MT_CHECKMATE:
5462     case MT_STAINMATE:
5463       if (WhiteOnMove(currentMove)) {
5464         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5465       } else {
5466         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5467       }
5468       break;
5469     case MT_STALEMATE:
5470       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5471       break;
5472     }
5473     break;
5474     
5475   case MachinePlaysBlack:
5476   case MachinePlaysWhite:
5477     /* disable certain menu options while machine is thinking */
5478     SetMachineThinkingEnables();
5479     break;
5480
5481   default:
5482     break;
5483   }
5484
5485   if(bookHit) { // [HGM] book: simulate book reply
5486         static char bookMove[MSG_SIZ]; // a bit generous?
5487
5488         programStats.nodes = programStats.depth = programStats.time = 
5489         programStats.score = programStats.got_only_move = 0;
5490         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5491
5492         strcpy(bookMove, "move ");
5493         strcat(bookMove, bookHit);
5494         HandleMachineMove(bookMove, &first);
5495   }
5496   return 1;
5497 }
5498
5499 void
5500 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5501      int fromX, fromY, toX, toY;
5502      int promoChar;
5503 {
5504     /* [HGM] This routine was added to allow calling of its two logical
5505        parts from other modules in the old way. Before, UserMoveEvent()
5506        automatically called FinishMove() if the move was OK, and returned
5507        otherwise. I separated the two, in order to make it possible to
5508        slip a promotion popup in between. But that it always needs two
5509        calls, to the first part, (now called UserMoveTest() ), and to
5510        FinishMove if the first part succeeded. Calls that do not need
5511        to do anything in between, can call this routine the old way. 
5512     */
5513     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5514 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5515     if(moveType == AmbiguousMove)
5516         DrawPosition(FALSE, boards[currentMove]);
5517     else if(moveType != ImpossibleMove && moveType != Comment)
5518         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5519 }
5520
5521 void LeftClick(ClickType clickType, int xPix, int yPix)
5522 {
5523     int x, y;
5524     Boolean saveAnimate;
5525     static int second = 0, promotionChoice = 0;
5526     char promoChoice = NULLCHAR;
5527
5528     if (clickType == Press) ErrorPopDown();
5529
5530     x = EventToSquare(xPix, BOARD_WIDTH);
5531     y = EventToSquare(yPix, BOARD_HEIGHT);
5532     if (!flipView && y >= 0) {
5533         y = BOARD_HEIGHT - 1 - y;
5534     }
5535     if (flipView && x >= 0) {
5536         x = BOARD_WIDTH - 1 - x;
5537     }
5538
5539     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5540         if(clickType == Release) return; // ignore upclick of click-click destination
5541         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5542         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5543         if(gameInfo.holdingsWidth && 
5544                 (WhiteOnMove(currentMove) 
5545                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5546                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5547             // click in right holdings, for determining promotion piece
5548             ChessSquare p = boards[currentMove][y][x];
5549             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5550             if(p != EmptySquare) {
5551                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5552                 fromX = fromY = -1;
5553                 return;
5554             }
5555         }
5556         DrawPosition(FALSE, boards[currentMove]);
5557         return;
5558     }
5559
5560     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5561     if(clickType == Press
5562             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5563               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5564               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5565         return;
5566
5567     if (fromX == -1) {
5568         if (clickType == Press) {
5569             /* First square */
5570             if (OKToStartUserMove(x, y)) {
5571                 fromX = x;
5572                 fromY = y;
5573                 second = 0;
5574                 DragPieceBegin(xPix, yPix);
5575                 if (appData.highlightDragging) {
5576                     SetHighlights(x, y, -1, -1);
5577                 }
5578             }
5579         }
5580         return;
5581     }
5582
5583     /* fromX != -1 */
5584     if (clickType == Press && gameMode != EditPosition) {
5585         ChessSquare fromP;
5586         ChessSquare toP;
5587         int frc;
5588
5589         // ignore off-board to clicks
5590         if(y < 0 || x < 0) return;
5591
5592         /* Check if clicking again on the same color piece */
5593         fromP = boards[currentMove][fromY][fromX];
5594         toP = boards[currentMove][y][x];
5595         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5596         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5597              WhitePawn <= toP && toP <= WhiteKing &&
5598              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5599              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5600             (BlackPawn <= fromP && fromP <= BlackKing && 
5601              BlackPawn <= toP && toP <= BlackKing &&
5602              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5603              !(fromP == BlackKing && toP == BlackRook && frc))) {
5604             /* Clicked again on same color piece -- changed his mind */
5605             second = (x == fromX && y == fromY);
5606             if (appData.highlightDragging) {
5607                 SetHighlights(x, y, -1, -1);
5608             } else {
5609                 ClearHighlights();
5610             }
5611             if (OKToStartUserMove(x, y)) {
5612                 fromX = x;
5613                 fromY = y;
5614                 DragPieceBegin(xPix, yPix);
5615             }
5616             return;
5617         }
5618         // ignore to-clicks in holdings
5619         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5620     }
5621
5622     if (clickType == Release && (x == fromX && y == fromY ||
5623         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5624
5625         // treat drags into holding as click on start square
5626         x = fromX; y = fromY;
5627
5628         DragPieceEnd(xPix, yPix);
5629         if (appData.animateDragging) {
5630             /* Undo animation damage if any */
5631             DrawPosition(FALSE, NULL);
5632         }
5633         if (second) {
5634             /* Second up/down in same square; just abort move */
5635             second = 0;
5636             fromX = fromY = -1;
5637             ClearHighlights();
5638             gotPremove = 0;
5639             ClearPremoveHighlights();
5640         } else {
5641             /* First upclick in same square; start click-click mode */
5642             SetHighlights(x, y, -1, -1);
5643         }
5644         return;
5645     }
5646
5647     /* we now have a different from- and to-square */
5648     /* Completed move */
5649     toX = x;
5650     toY = y;
5651     saveAnimate = appData.animate;
5652     if (clickType == Press) {
5653         /* Finish clickclick move */
5654         if (appData.animate || appData.highlightLastMove) {
5655             SetHighlights(fromX, fromY, toX, toY);
5656         } else {
5657             ClearHighlights();
5658         }
5659     } else {
5660         /* Finish drag move */
5661         if (appData.highlightLastMove) {
5662             SetHighlights(fromX, fromY, toX, toY);
5663         } else {
5664             ClearHighlights();
5665         }
5666         DragPieceEnd(xPix, yPix);
5667         /* Don't animate move and drag both */
5668         appData.animate = FALSE;
5669     }
5670     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5671         SetHighlights(fromX, fromY, toX, toY);
5672         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5673             // [HGM] super: promotion to captured piece selected from holdings
5674             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5675             promotionChoice = TRUE;
5676             // kludge follows to temporarily execute move on display, without promoting yet
5677             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5678             boards[currentMove][toY][toX] = p;
5679             DrawPosition(FALSE, boards[currentMove]);
5680             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5681             boards[currentMove][toY][toX] = q;
5682             DisplayMessage("Click in holdings to choose piece", "");
5683             return;
5684         }
5685         PromotionPopUp();
5686     } else {
5687         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5688         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5689         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5690         fromX = fromY = -1;
5691     }
5692     appData.animate = saveAnimate;
5693     if (appData.animate || appData.animateDragging) {
5694         /* Undo animation damage if needed */
5695         DrawPosition(FALSE, NULL);
5696     }
5697 }
5698
5699 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5700 {
5701 //    char * hint = lastHint;
5702     FrontEndProgramStats stats;
5703
5704     stats.which = cps == &first ? 0 : 1;
5705     stats.depth = cpstats->depth;
5706     stats.nodes = cpstats->nodes;
5707     stats.score = cpstats->score;
5708     stats.time = cpstats->time;
5709     stats.pv = cpstats->movelist;
5710     stats.hint = lastHint;
5711     stats.an_move_index = 0;
5712     stats.an_move_count = 0;
5713
5714     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5715         stats.hint = cpstats->move_name;
5716         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5717         stats.an_move_count = cpstats->nr_moves;
5718     }
5719
5720     SetProgramStats( &stats );
5721 }
5722
5723 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5724 {   // [HGM] book: this routine intercepts moves to simulate book replies
5725     char *bookHit = NULL;
5726
5727     //first determine if the incoming move brings opponent into his book
5728     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5729         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5730     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5731     if(bookHit != NULL && !cps->bookSuspend) {
5732         // make sure opponent is not going to reply after receiving move to book position
5733         SendToProgram("force\n", cps);
5734         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5735     }
5736     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5737     // now arrange restart after book miss
5738     if(bookHit) {
5739         // after a book hit we never send 'go', and the code after the call to this routine
5740         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5741         char buf[MSG_SIZ];
5742         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5743         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5744         SendToProgram(buf, cps);
5745         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5746     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5747         SendToProgram("go\n", cps);
5748         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5749     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5750         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5751             SendToProgram("go\n", cps); 
5752         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5753     }
5754     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5755 }
5756
5757 char *savedMessage;
5758 ChessProgramState *savedState;
5759 void DeferredBookMove(void)
5760 {
5761         if(savedState->lastPing != savedState->lastPong)
5762                     ScheduleDelayedEvent(DeferredBookMove, 10);
5763         else
5764         HandleMachineMove(savedMessage, savedState);
5765 }
5766
5767 void
5768 HandleMachineMove(message, cps)
5769      char *message;
5770      ChessProgramState *cps;
5771 {
5772     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5773     char realname[MSG_SIZ];
5774     int fromX, fromY, toX, toY;
5775     ChessMove moveType;
5776     char promoChar;
5777     char *p;
5778     int machineWhite;
5779     char *bookHit;
5780
5781 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5782     /*
5783      * Kludge to ignore BEL characters
5784      */
5785     while (*message == '\007') message++;
5786
5787     /*
5788      * [HGM] engine debug message: ignore lines starting with '#' character
5789      */
5790     if(cps->debug && *message == '#') return;
5791
5792     /*
5793      * Look for book output
5794      */
5795     if (cps == &first && bookRequested) {
5796         if (message[0] == '\t' || message[0] == ' ') {
5797             /* Part of the book output is here; append it */
5798             strcat(bookOutput, message);
5799             strcat(bookOutput, "  \n");
5800             return;
5801         } else if (bookOutput[0] != NULLCHAR) {
5802             /* All of book output has arrived; display it */
5803             char *p = bookOutput;
5804             while (*p != NULLCHAR) {
5805                 if (*p == '\t') *p = ' ';
5806                 p++;
5807             }
5808             DisplayInformation(bookOutput);
5809             bookRequested = FALSE;
5810             /* Fall through to parse the current output */
5811         }
5812     }
5813
5814     /*
5815      * Look for machine move.
5816      */
5817     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5818         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5819     {
5820         /* This method is only useful on engines that support ping */
5821         if (cps->lastPing != cps->lastPong) {
5822           if (gameMode == BeginningOfGame) {
5823             /* Extra move from before last new; ignore */
5824             if (appData.debugMode) {
5825                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5826             }
5827           } else {
5828             if (appData.debugMode) {
5829                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5830                         cps->which, gameMode);
5831             }
5832
5833             SendToProgram("undo\n", cps);
5834           }
5835           return;
5836         }
5837
5838         switch (gameMode) {
5839           case BeginningOfGame:
5840             /* Extra move from before last reset; ignore */
5841             if (appData.debugMode) {
5842                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5843             }
5844             return;
5845
5846           case EndOfGame:
5847           case IcsIdle:
5848           default:
5849             /* Extra move after we tried to stop.  The mode test is
5850                not a reliable way of detecting this problem, but it's
5851                the best we can do on engines that don't support ping.
5852             */
5853             if (appData.debugMode) {
5854                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5855                         cps->which, gameMode);
5856             }
5857             SendToProgram("undo\n", cps);
5858             return;
5859
5860           case MachinePlaysWhite:
5861           case IcsPlayingWhite:
5862             machineWhite = TRUE;
5863             break;
5864
5865           case MachinePlaysBlack:
5866           case IcsPlayingBlack:
5867             machineWhite = FALSE;
5868             break;
5869
5870           case TwoMachinesPlay:
5871             machineWhite = (cps->twoMachinesColor[0] == 'w');
5872             break;
5873         }
5874         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5875             if (appData.debugMode) {
5876                 fprintf(debugFP,
5877                         "Ignoring move out of turn by %s, gameMode %d"
5878                         ", forwardMost %d\n",
5879                         cps->which, gameMode, forwardMostMove);
5880             }
5881             return;
5882         }
5883
5884     if (appData.debugMode) { int f = forwardMostMove;
5885         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5886                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5887     }
5888         if(cps->alphaRank) AlphaRank(machineMove, 4);
5889         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5890                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5891             /* Machine move could not be parsed; ignore it. */
5892             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5893                     machineMove, cps->which);
5894             DisplayError(buf1, 0);
5895             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5896                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5897             if (gameMode == TwoMachinesPlay) {
5898               GameEnds(machineWhite ? BlackWins : WhiteWins,
5899                        buf1, GE_XBOARD);
5900             }
5901             return;
5902         }
5903
5904         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5905         /* So we have to redo legality test with true e.p. status here,  */
5906         /* to make sure an illegal e.p. capture does not slip through,   */
5907         /* to cause a forfeit on a justified illegal-move complaint      */
5908         /* of the opponent.                                              */
5909         if( gameMode==TwoMachinesPlay && appData.testLegality
5910             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5911                                                               ) {
5912            ChessMove moveType;
5913            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5914                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5915                              fromY, fromX, toY, toX, promoChar);
5916             if (appData.debugMode) {
5917                 int i;
5918                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5919                     castlingRights[forwardMostMove][i], castlingRank[i]);
5920                 fprintf(debugFP, "castling rights\n");
5921             }
5922             if(moveType == IllegalMove) {
5923                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5924                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5925                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5926                            buf1, GE_XBOARD);
5927                 return;
5928            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5929            /* [HGM] Kludge to handle engines that send FRC-style castling
5930               when they shouldn't (like TSCP-Gothic) */
5931            switch(moveType) {
5932              case WhiteASideCastleFR:
5933              case BlackASideCastleFR:
5934                toX+=2;
5935                currentMoveString[2]++;
5936                break;
5937              case WhiteHSideCastleFR:
5938              case BlackHSideCastleFR:
5939                toX--;
5940                currentMoveString[2]--;
5941                break;
5942              default: ; // nothing to do, but suppresses warning of pedantic compilers
5943            }
5944         }
5945         hintRequested = FALSE;
5946         lastHint[0] = NULLCHAR;
5947         bookRequested = FALSE;
5948         /* Program may be pondering now */
5949         cps->maybeThinking = TRUE;
5950         if (cps->sendTime == 2) cps->sendTime = 1;
5951         if (cps->offeredDraw) cps->offeredDraw--;
5952
5953 #if ZIPPY
5954         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5955             first.initDone) {
5956           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5957           ics_user_moved = 1;
5958           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5959                 char buf[3*MSG_SIZ];
5960
5961                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5962                         programStats.score / 100.,
5963                         programStats.depth,
5964                         programStats.time / 100.,
5965                         (unsigned int)programStats.nodes,
5966                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5967                         programStats.movelist);
5968                 SendToICS(buf);
5969 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5970           }
5971         }
5972 #endif
5973         /* currentMoveString is set as a side-effect of ParseOneMove */
5974         strcpy(machineMove, currentMoveString);
5975         strcat(machineMove, "\n");
5976         strcpy(moveList[forwardMostMove], machineMove);
5977
5978         /* [AS] Save move info and clear stats for next move */
5979         pvInfoList[ forwardMostMove ].score = programStats.score;
5980         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5981         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5982         ClearProgramStats();
5983         thinkOutput[0] = NULLCHAR;
5984         hiddenThinkOutputState = 0;
5985
5986         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5987
5988         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5989         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5990             int count = 0;
5991
5992             while( count < adjudicateLossPlies ) {
5993                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5994
5995                 if( count & 1 ) {
5996                     score = -score; /* Flip score for winning side */
5997                 }
5998
5999                 if( score > adjudicateLossThreshold ) {
6000                     break;
6001                 }
6002
6003                 count++;
6004             }
6005
6006             if( count >= adjudicateLossPlies ) {
6007                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6008
6009                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6010                     "Xboard adjudication", 
6011                     GE_XBOARD );
6012
6013                 return;
6014             }
6015         }
6016
6017         if( gameMode == TwoMachinesPlay ) {
6018           // [HGM] some adjudications useful with buggy engines
6019             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6020           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6021
6022
6023             if( appData.testLegality )
6024             {   /* [HGM] Some more adjudications for obstinate engines */
6025                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6026                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6027                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6028                 static int moveCount = 6;
6029                 ChessMove result;
6030                 char *reason = NULL;
6031
6032                 /* Count what is on board. */
6033                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6034                 {   ChessSquare p = boards[forwardMostMove][i][j];
6035                     int m=i;
6036
6037                     switch((int) p)
6038                     {   /* count B,N,R and other of each side */
6039                         case WhiteKing:
6040                         case BlackKing:
6041                              NrK++; break; // [HGM] atomic: count Kings
6042                         case WhiteKnight:
6043                              NrWN++; break;
6044                         case WhiteBishop:
6045                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6046                              bishopsColor |= 1 << ((i^j)&1);
6047                              NrWB++; break;
6048                         case BlackKnight:
6049                              NrBN++; break;
6050                         case BlackBishop:
6051                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6052                              bishopsColor |= 1 << ((i^j)&1);
6053                              NrBB++; break;
6054                         case WhiteRook:
6055                              NrWR++; break;
6056                         case BlackRook:
6057                              NrBR++; break;
6058                         case WhiteQueen:
6059                              NrWQ++; break;
6060                         case BlackQueen:
6061                              NrBQ++; break;
6062                         case EmptySquare: 
6063                              break;
6064                         case BlackPawn:
6065                              m = 7-i;
6066                         case WhitePawn:
6067                              PawnAdvance += m; NrPawns++;
6068                     }
6069                     NrPieces += (p != EmptySquare);
6070                     NrW += ((int)p < (int)BlackPawn);
6071                     if(gameInfo.variant == VariantXiangqi && 
6072                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6073                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6074                         NrW -= ((int)p < (int)BlackPawn);
6075                     }
6076                 }
6077
6078                 /* Some material-based adjudications that have to be made before stalemate test */
6079                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6080                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6081                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6082                      if(appData.checkMates) {
6083                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6084                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6085                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6086                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6087                          return;
6088                      }
6089                 }
6090
6091                 /* Bare King in Shatranj (loses) or Losers (wins) */
6092                 if( NrW == 1 || NrPieces - NrW == 1) {
6093                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6094                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6095                      if(appData.checkMates) {
6096                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6097                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6098                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6099                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6100                          return;
6101                      }
6102                   } else
6103                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6104                   {    /* bare King */
6105                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6106                         if(appData.checkMates) {
6107                             /* but only adjudicate if adjudication enabled */
6108                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6109                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6110                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6111                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6112                             return;
6113                         }
6114                   }
6115                 } else bare = 1;
6116
6117
6118             // don't wait for engine to announce game end if we can judge ourselves
6119             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6120                                        castlingRights[forwardMostMove]) ) {
6121               case MT_CHECK:
6122                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6123                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6124                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6125                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6126                             checkCnt++;
6127                         if(checkCnt >= 2) {
6128                             reason = "Xboard adjudication: 3rd check";
6129                             epStatus[forwardMostMove] = EP_CHECKMATE;
6130                             break;
6131                         }
6132                     }
6133                 }
6134               case MT_NONE:
6135               default:
6136                 break;
6137               case MT_STALEMATE:
6138               case MT_STAINMATE:
6139                 reason = "Xboard adjudication: Stalemate";
6140                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6141                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6142                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6143                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6144                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6145                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6146                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6147                                                                         EP_CHECKMATE : EP_WINS);
6148                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6149                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6150                 }
6151                 break;
6152               case MT_CHECKMATE:
6153                 reason = "Xboard adjudication: Checkmate";
6154                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6155                 break;
6156             }
6157
6158                 switch(i = epStatus[forwardMostMove]) {
6159                     case EP_STALEMATE:
6160                         result = GameIsDrawn; break;
6161                     case EP_CHECKMATE:
6162                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6163                     case EP_WINS:
6164                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6165                     default:
6166                         result = (ChessMove) 0;
6167                 }
6168                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6169                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6170                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6171                     GameEnds( result, reason, GE_XBOARD );
6172                     return;
6173                 }
6174
6175                 /* Next absolutely insufficient mating material. */
6176                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6177                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6178                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6179                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6180                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6181
6182                      /* always flag draws, for judging claims */
6183                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6184
6185                      if(appData.materialDraws) {
6186                          /* but only adjudicate them if adjudication enabled */
6187                          SendToProgram("force\n", cps->other); // suppress reply
6188                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6189                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6190                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6191                          return;
6192                      }
6193                 }
6194
6195                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6196                 if(NrPieces == 4 && 
6197                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6198                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6199                    || NrWN==2 || NrBN==2     /* KNNK */
6200                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6201                   ) ) {
6202                      if(--moveCount < 0 && appData.trivialDraws)
6203                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6204                           SendToProgram("force\n", cps->other); // suppress reply
6205                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6206                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6208                           return;
6209                      }
6210                 } else moveCount = 6;
6211             }
6212           }
6213           
6214           if (appData.debugMode) { int i;
6215             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6216                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6217                     appData.drawRepeats);
6218             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6219               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6220             
6221           }
6222
6223                 /* Check for rep-draws */
6224                 count = 0;
6225                 for(k = forwardMostMove-2;
6226                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6227                         epStatus[k] < EP_UNKNOWN &&
6228                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6229                     k-=2)
6230                 {   int rights=0;
6231                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6232                         /* compare castling rights */
6233                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6234                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6235                                 rights++; /* King lost rights, while rook still had them */
6236                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6237                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6238                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6239                                    rights++; /* but at least one rook lost them */
6240                         }
6241                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6242                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6243                                 rights++; 
6244                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6245                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6246                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6247                                    rights++;
6248                         }
6249                         if( rights == 0 && ++count > appData.drawRepeats-2
6250                             && appData.drawRepeats > 1) {
6251                              /* adjudicate after user-specified nr of repeats */
6252                              SendToProgram("force\n", cps->other); // suppress reply
6253                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6254                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6255                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6256                                 // [HGM] xiangqi: check for forbidden perpetuals
6257                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6258                                 for(m=forwardMostMove; m>k; m-=2) {
6259                                     if(MateTest(boards[m], PosFlags(m), 
6260                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6261                                         ourPerpetual = 0; // the current mover did not always check
6262                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6263                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6264                                         hisPerpetual = 0; // the opponent did not always check
6265                                 }
6266                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6267                                                                         ourPerpetual, hisPerpetual);
6268                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6269                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6270                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6271                                     return;
6272                                 }
6273                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6274                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6275                                 // Now check for perpetual chases
6276                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6277                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6278                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6279                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6280                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6281                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6282                                         return;
6283                                     }
6284                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6285                                         break; // Abort repetition-checking loop.
6286                                 }
6287                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6288                              }
6289                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6290                              return;
6291                         }
6292                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6293                              epStatus[forwardMostMove] = EP_REP_DRAW;
6294                     }
6295                 }
6296
6297                 /* Now we test for 50-move draws. Determine ply count */
6298                 count = forwardMostMove;
6299                 /* look for last irreversble move */
6300                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6301                     count--;
6302                 /* if we hit starting position, add initial plies */
6303                 if( count == backwardMostMove )
6304                     count -= initialRulePlies;
6305                 count = forwardMostMove - count; 
6306                 if( count >= 100)
6307                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6308                          /* this is used to judge if draw claims are legal */
6309                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6310                          SendToProgram("force\n", cps->other); // suppress reply
6311                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6312                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6313                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6314                          return;
6315                 }
6316
6317                 /* if draw offer is pending, treat it as a draw claim
6318                  * when draw condition present, to allow engines a way to
6319                  * claim draws before making their move to avoid a race
6320                  * condition occurring after their move
6321                  */
6322                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6323                          char *p = NULL;
6324                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6325                              p = "Draw claim: 50-move rule";
6326                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6327                              p = "Draw claim: 3-fold repetition";
6328                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6329                              p = "Draw claim: insufficient mating material";
6330                          if( p != NULL ) {
6331                              SendToProgram("force\n", cps->other); // suppress reply
6332                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6333                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6334                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6335                              return;
6336                          }
6337                 }
6338
6339
6340                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6341                     SendToProgram("force\n", cps->other); // suppress reply
6342                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6343                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6344
6345                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6346
6347                     return;
6348                 }
6349         }
6350
6351         bookHit = NULL;
6352         if (gameMode == TwoMachinesPlay) {
6353             /* [HGM] relaying draw offers moved to after reception of move */
6354             /* and interpreting offer as claim if it brings draw condition */
6355             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6356                 SendToProgram("draw\n", cps->other);
6357             }
6358             if (cps->other->sendTime) {
6359                 SendTimeRemaining(cps->other,
6360                                   cps->other->twoMachinesColor[0] == 'w');
6361             }
6362             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6363             if (firstMove && !bookHit) {
6364                 firstMove = FALSE;
6365                 if (cps->other->useColors) {
6366                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6367                 }
6368                 SendToProgram("go\n", cps->other);
6369             }
6370             cps->other->maybeThinking = TRUE;
6371         }
6372
6373         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6374         
6375         if (!pausing && appData.ringBellAfterMoves) {
6376             RingBell();
6377         }
6378
6379         /* 
6380          * Reenable menu items that were disabled while
6381          * machine was thinking
6382          */
6383         if (gameMode != TwoMachinesPlay)
6384             SetUserThinkingEnables();
6385
6386         // [HGM] book: after book hit opponent has received move and is now in force mode
6387         // force the book reply into it, and then fake that it outputted this move by jumping
6388         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6389         if(bookHit) {
6390                 static char bookMove[MSG_SIZ]; // a bit generous?
6391
6392                 strcpy(bookMove, "move ");
6393                 strcat(bookMove, bookHit);
6394                 message = bookMove;
6395                 cps = cps->other;
6396                 programStats.nodes = programStats.depth = programStats.time = 
6397                 programStats.score = programStats.got_only_move = 0;
6398                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6399
6400                 if(cps->lastPing != cps->lastPong) {
6401                     savedMessage = message; // args for deferred call
6402                     savedState = cps;
6403                     ScheduleDelayedEvent(DeferredBookMove, 10);
6404                     return;
6405                 }
6406                 goto FakeBookMove;
6407         }
6408
6409         return;
6410     }
6411
6412     /* Set special modes for chess engines.  Later something general
6413      *  could be added here; for now there is just one kludge feature,
6414      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6415      *  when "xboard" is given as an interactive command.
6416      */
6417     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6418         cps->useSigint = FALSE;
6419         cps->useSigterm = FALSE;
6420     }
6421     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6422       ParseFeatures(message+8, cps);
6423       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6424     }
6425
6426     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6427      * want this, I was asked to put it in, and obliged.
6428      */
6429     if (!strncmp(message, "setboard ", 9)) {
6430         Board initial_position; int i;
6431
6432         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6433
6434         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6435             DisplayError(_("Bad FEN received from engine"), 0);
6436             return ;
6437         } else {
6438            Reset(TRUE, FALSE);
6439            CopyBoard(boards[0], initial_position);
6440            initialRulePlies = FENrulePlies;
6441            epStatus[0] = FENepStatus;
6442            for( i=0; i<nrCastlingRights; i++ )
6443                 castlingRights[0][i] = FENcastlingRights[i];
6444            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6445            else gameMode = MachinePlaysBlack;                 
6446            DrawPosition(FALSE, boards[currentMove]);
6447         }
6448         return;
6449     }
6450
6451     /*
6452      * Look for communication commands
6453      */
6454     if (!strncmp(message, "telluser ", 9)) {
6455         DisplayNote(message + 9);
6456         return;
6457     }
6458     if (!strncmp(message, "tellusererror ", 14)) {
6459         DisplayError(message + 14, 0);
6460         return;
6461     }
6462     if (!strncmp(message, "tellopponent ", 13)) {
6463       if (appData.icsActive) {
6464         if (loggedOn) {
6465           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6466           SendToICS(buf1);
6467         }
6468       } else {
6469         DisplayNote(message + 13);
6470       }
6471       return;
6472     }
6473     if (!strncmp(message, "tellothers ", 11)) {
6474       if (appData.icsActive) {
6475         if (loggedOn) {
6476           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6477           SendToICS(buf1);
6478         }
6479       }
6480       return;
6481     }
6482     if (!strncmp(message, "tellall ", 8)) {
6483       if (appData.icsActive) {
6484         if (loggedOn) {
6485           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6486           SendToICS(buf1);
6487         }
6488       } else {
6489         DisplayNote(message + 8);
6490       }
6491       return;
6492     }
6493     if (strncmp(message, "warning", 7) == 0) {
6494         /* Undocumented feature, use tellusererror in new code */
6495         DisplayError(message, 0);
6496         return;
6497     }
6498     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6499         strcpy(realname, cps->tidy);
6500         strcat(realname, " query");
6501         AskQuestion(realname, buf2, buf1, cps->pr);
6502         return;
6503     }
6504     /* Commands from the engine directly to ICS.  We don't allow these to be 
6505      *  sent until we are logged on. Crafty kibitzes have been known to 
6506      *  interfere with the login process.
6507      */
6508     if (loggedOn) {
6509         if (!strncmp(message, "tellics ", 8)) {
6510             SendToICS(message + 8);
6511             SendToICS("\n");
6512             return;
6513         }
6514         if (!strncmp(message, "tellicsnoalias ", 15)) {
6515             SendToICS(ics_prefix);
6516             SendToICS(message + 15);
6517             SendToICS("\n");
6518             return;
6519         }
6520         /* The following are for backward compatibility only */
6521         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6522             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6523             SendToICS(ics_prefix);
6524             SendToICS(message);
6525             SendToICS("\n");
6526             return;
6527         }
6528     }
6529     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6530         return;
6531     }
6532     /*
6533      * If the move is illegal, cancel it and redraw the board.
6534      * Also deal with other error cases.  Matching is rather loose
6535      * here to accommodate engines written before the spec.
6536      */
6537     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6538         strncmp(message, "Error", 5) == 0) {
6539         if (StrStr(message, "name") || 
6540             StrStr(message, "rating") || StrStr(message, "?") ||
6541             StrStr(message, "result") || StrStr(message, "board") ||
6542             StrStr(message, "bk") || StrStr(message, "computer") ||
6543             StrStr(message, "variant") || StrStr(message, "hint") ||
6544             StrStr(message, "random") || StrStr(message, "depth") ||
6545             StrStr(message, "accepted")) {
6546             return;
6547         }
6548         if (StrStr(message, "protover")) {
6549           /* Program is responding to input, so it's apparently done
6550              initializing, and this error message indicates it is
6551              protocol version 1.  So we don't need to wait any longer
6552              for it to initialize and send feature commands. */
6553           FeatureDone(cps, 1);
6554           cps->protocolVersion = 1;
6555           return;
6556         }
6557         cps->maybeThinking = FALSE;
6558
6559         if (StrStr(message, "draw")) {
6560             /* Program doesn't have "draw" command */
6561             cps->sendDrawOffers = 0;
6562             return;
6563         }
6564         if (cps->sendTime != 1 &&
6565             (StrStr(message, "time") || StrStr(message, "otim"))) {
6566           /* Program apparently doesn't have "time" or "otim" command */
6567           cps->sendTime = 0;
6568           return;
6569         }
6570         if (StrStr(message, "analyze")) {
6571             cps->analysisSupport = FALSE;
6572             cps->analyzing = FALSE;
6573             Reset(FALSE, TRUE);
6574             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6575             DisplayError(buf2, 0);
6576             return;
6577         }
6578         if (StrStr(message, "(no matching move)st")) {
6579           /* Special kludge for GNU Chess 4 only */
6580           cps->stKludge = TRUE;
6581           SendTimeControl(cps, movesPerSession, timeControl,
6582                           timeIncrement, appData.searchDepth,
6583                           searchTime);
6584           return;
6585         }
6586         if (StrStr(message, "(no matching move)sd")) {
6587           /* Special kludge for GNU Chess 4 only */
6588           cps->sdKludge = TRUE;
6589           SendTimeControl(cps, movesPerSession, timeControl,
6590                           timeIncrement, appData.searchDepth,
6591                           searchTime);
6592           return;
6593         }
6594         if (!StrStr(message, "llegal")) {
6595             return;
6596         }
6597         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6598             gameMode == IcsIdle) return;
6599         if (forwardMostMove <= backwardMostMove) return;
6600         if (pausing) PauseEvent();
6601       if(appData.forceIllegal) {
6602             // [HGM] illegal: machine refused move; force position after move into it
6603           SendToProgram("force\n", cps);
6604           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6605                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6606                 // when black is to move, while there might be nothing on a2 or black
6607                 // might already have the move. So send the board as if white has the move.
6608                 // But first we must change the stm of the engine, as it refused the last move
6609                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6610                 if(WhiteOnMove(forwardMostMove)) {
6611                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6612                     SendBoard(cps, forwardMostMove); // kludgeless board
6613                 } else {
6614                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6615                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6616                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6617                 }
6618           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6619             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6620                  gameMode == TwoMachinesPlay)
6621               SendToProgram("go\n", cps);
6622             return;
6623       } else
6624         if (gameMode == PlayFromGameFile) {
6625             /* Stop reading this game file */
6626             gameMode = EditGame;
6627             ModeHighlight();
6628         }
6629         currentMove = --forwardMostMove;
6630         DisplayMove(currentMove-1); /* before DisplayMoveError */
6631         SwitchClocks();
6632         DisplayBothClocks();
6633         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6634                 parseList[currentMove], cps->which);
6635         DisplayMoveError(buf1);
6636         DrawPosition(FALSE, boards[currentMove]);
6637
6638         /* [HGM] illegal-move claim should forfeit game when Xboard */
6639         /* only passes fully legal moves                            */
6640         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6641             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6642                                 "False illegal-move claim", GE_XBOARD );
6643         }
6644         return;
6645     }
6646     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6647         /* Program has a broken "time" command that
6648            outputs a string not ending in newline.
6649            Don't use it. */
6650         cps->sendTime = 0;
6651     }
6652     
6653     /*
6654      * If chess program startup fails, exit with an error message.
6655      * Attempts to recover here are futile.
6656      */
6657     if ((StrStr(message, "unknown host") != NULL)
6658         || (StrStr(message, "No remote directory") != NULL)
6659         || (StrStr(message, "not found") != NULL)
6660         || (StrStr(message, "No such file") != NULL)
6661         || (StrStr(message, "can't alloc") != NULL)
6662         || (StrStr(message, "Permission denied") != NULL)) {
6663
6664         cps->maybeThinking = FALSE;
6665         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6666                 cps->which, cps->program, cps->host, message);
6667         RemoveInputSource(cps->isr);
6668         DisplayFatalError(buf1, 0, 1);
6669         return;
6670     }
6671     
6672     /* 
6673      * Look for hint output
6674      */
6675     if (sscanf(message, "Hint: %s", buf1) == 1) {
6676         if (cps == &first && hintRequested) {
6677             hintRequested = FALSE;
6678             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6679                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6680                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6681                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6682                                     fromY, fromX, toY, toX, promoChar, buf1);
6683                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6684                 DisplayInformation(buf2);
6685             } else {
6686                 /* Hint move could not be parsed!? */
6687               snprintf(buf2, sizeof(buf2),
6688                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6689                         buf1, cps->which);
6690                 DisplayError(buf2, 0);
6691             }
6692         } else {
6693             strcpy(lastHint, buf1);
6694         }
6695         return;
6696     }
6697
6698     /*
6699      * Ignore other messages if game is not in progress
6700      */
6701     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6702         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6703
6704     /*
6705      * look for win, lose, draw, or draw offer
6706      */
6707     if (strncmp(message, "1-0", 3) == 0) {
6708         char *p, *q, *r = "";
6709         p = strchr(message, '{');
6710         if (p) {
6711             q = strchr(p, '}');
6712             if (q) {
6713                 *q = NULLCHAR;
6714                 r = p + 1;
6715             }
6716         }
6717         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6718         return;
6719     } else if (strncmp(message, "0-1", 3) == 0) {
6720         char *p, *q, *r = "";
6721         p = strchr(message, '{');
6722         if (p) {
6723             q = strchr(p, '}');
6724             if (q) {
6725                 *q = NULLCHAR;
6726                 r = p + 1;
6727             }
6728         }
6729         /* Kludge for Arasan 4.1 bug */
6730         if (strcmp(r, "Black resigns") == 0) {
6731             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6732             return;
6733         }
6734         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6735         return;
6736     } else if (strncmp(message, "1/2", 3) == 0) {
6737         char *p, *q, *r = "";
6738         p = strchr(message, '{');
6739         if (p) {
6740             q = strchr(p, '}');
6741             if (q) {
6742                 *q = NULLCHAR;
6743                 r = p + 1;
6744             }
6745         }
6746             
6747         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6748         return;
6749
6750     } else if (strncmp(message, "White resign", 12) == 0) {
6751         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6752         return;
6753     } else if (strncmp(message, "Black resign", 12) == 0) {
6754         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6755         return;
6756     } else if (strncmp(message, "White matches", 13) == 0 ||
6757                strncmp(message, "Black matches", 13) == 0   ) {
6758         /* [HGM] ignore GNUShogi noises */
6759         return;
6760     } else if (strncmp(message, "White", 5) == 0 &&
6761                message[5] != '(' &&
6762                StrStr(message, "Black") == NULL) {
6763         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6764         return;
6765     } else if (strncmp(message, "Black", 5) == 0 &&
6766                message[5] != '(') {
6767         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6768         return;
6769     } else if (strcmp(message, "resign") == 0 ||
6770                strcmp(message, "computer resigns") == 0) {
6771         switch (gameMode) {
6772           case MachinePlaysBlack:
6773           case IcsPlayingBlack:
6774             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6775             break;
6776           case MachinePlaysWhite:
6777           case IcsPlayingWhite:
6778             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6779             break;
6780           case TwoMachinesPlay:
6781             if (cps->twoMachinesColor[0] == 'w')
6782               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6783             else
6784               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6785             break;
6786           default:
6787             /* can't happen */
6788             break;
6789         }
6790         return;
6791     } else if (strncmp(message, "opponent mates", 14) == 0) {
6792         switch (gameMode) {
6793           case MachinePlaysBlack:
6794           case IcsPlayingBlack:
6795             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6796             break;
6797           case MachinePlaysWhite:
6798           case IcsPlayingWhite:
6799             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6800             break;
6801           case TwoMachinesPlay:
6802             if (cps->twoMachinesColor[0] == 'w')
6803               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6804             else
6805               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6806             break;
6807           default:
6808             /* can't happen */
6809             break;
6810         }
6811         return;
6812     } else if (strncmp(message, "computer mates", 14) == 0) {
6813         switch (gameMode) {
6814           case MachinePlaysBlack:
6815           case IcsPlayingBlack:
6816             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6817             break;
6818           case MachinePlaysWhite:
6819           case IcsPlayingWhite:
6820             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6821             break;
6822           case TwoMachinesPlay:
6823             if (cps->twoMachinesColor[0] == 'w')
6824               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6825             else
6826               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6827             break;
6828           default:
6829             /* can't happen */
6830             break;
6831         }
6832         return;
6833     } else if (strncmp(message, "checkmate", 9) == 0) {
6834         if (WhiteOnMove(forwardMostMove)) {
6835             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6836         } else {
6837             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6838         }
6839         return;
6840     } else if (strstr(message, "Draw") != NULL ||
6841                strstr(message, "game is a draw") != NULL) {
6842         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6843         return;
6844     } else if (strstr(message, "offer") != NULL &&
6845                strstr(message, "draw") != NULL) {
6846 #if ZIPPY
6847         if (appData.zippyPlay && first.initDone) {
6848             /* Relay offer to ICS */
6849             SendToICS(ics_prefix);
6850             SendToICS("draw\n");
6851         }
6852 #endif
6853         cps->offeredDraw = 2; /* valid until this engine moves twice */
6854         if (gameMode == TwoMachinesPlay) {
6855             if (cps->other->offeredDraw) {
6856                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6857             /* [HGM] in two-machine mode we delay relaying draw offer      */
6858             /* until after we also have move, to see if it is really claim */
6859             }
6860         } else if (gameMode == MachinePlaysWhite ||
6861                    gameMode == MachinePlaysBlack) {
6862           if (userOfferedDraw) {
6863             DisplayInformation(_("Machine accepts your draw offer"));
6864             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6865           } else {
6866             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6867           }
6868         }
6869     }
6870
6871     
6872     /*
6873      * Look for thinking output
6874      */
6875     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6876           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6877                                 ) {
6878         int plylev, mvleft, mvtot, curscore, time;
6879         char mvname[MOVE_LEN];
6880         u64 nodes; // [DM]
6881         char plyext;
6882         int ignore = FALSE;
6883         int prefixHint = FALSE;
6884         mvname[0] = NULLCHAR;
6885
6886         switch (gameMode) {
6887           case MachinePlaysBlack:
6888           case IcsPlayingBlack:
6889             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6890             break;
6891           case MachinePlaysWhite:
6892           case IcsPlayingWhite:
6893             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6894             break;
6895           case AnalyzeMode:
6896           case AnalyzeFile:
6897             break;
6898           case IcsObserving: /* [DM] icsEngineAnalyze */
6899             if (!appData.icsEngineAnalyze) ignore = TRUE;
6900             break;
6901           case TwoMachinesPlay:
6902             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6903                 ignore = TRUE;
6904             }
6905             break;
6906           default:
6907             ignore = TRUE;
6908             break;
6909         }
6910
6911         if (!ignore) {
6912             buf1[0] = NULLCHAR;
6913             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6914                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6915
6916                 if (plyext != ' ' && plyext != '\t') {
6917                     time *= 100;
6918                 }
6919
6920                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6921                 if( cps->scoreIsAbsolute && 
6922                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6923                 {
6924                     curscore = -curscore;
6925                 }
6926
6927
6928                 programStats.depth = plylev;
6929                 programStats.nodes = nodes;
6930                 programStats.time = time;
6931                 programStats.score = curscore;
6932                 programStats.got_only_move = 0;
6933
6934                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6935                         int ticklen;
6936
6937                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6938                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6939                         if(WhiteOnMove(forwardMostMove)) 
6940                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6941                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6942                 }
6943
6944                 /* Buffer overflow protection */
6945                 if (buf1[0] != NULLCHAR) {
6946                     if (strlen(buf1) >= sizeof(programStats.movelist)
6947                         && appData.debugMode) {
6948                         fprintf(debugFP,
6949                                 "PV is too long; using the first %d bytes.\n",
6950                                 sizeof(programStats.movelist) - 1);
6951                     }
6952
6953                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6954                 } else {
6955                     sprintf(programStats.movelist, " no PV\n");
6956                 }
6957
6958                 if (programStats.seen_stat) {
6959                     programStats.ok_to_send = 1;
6960                 }
6961
6962                 if (strchr(programStats.movelist, '(') != NULL) {
6963                     programStats.line_is_book = 1;
6964                     programStats.nr_moves = 0;
6965                     programStats.moves_left = 0;
6966                 } else {
6967                     programStats.line_is_book = 0;
6968                 }
6969
6970                 SendProgramStatsToFrontend( cps, &programStats );
6971
6972                 /* 
6973                     [AS] Protect the thinkOutput buffer from overflow... this
6974                     is only useful if buf1 hasn't overflowed first!
6975                 */
6976                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6977                         plylev, 
6978                         (gameMode == TwoMachinesPlay ?
6979                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6980                         ((double) curscore) / 100.0,
6981                         prefixHint ? lastHint : "",
6982                         prefixHint ? " " : "" );
6983
6984                 if( buf1[0] != NULLCHAR ) {
6985                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6986
6987                     if( strlen(buf1) > max_len ) {
6988                         if( appData.debugMode) {
6989                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6990                         }
6991                         buf1[max_len+1] = '\0';
6992                     }
6993
6994                     strcat( thinkOutput, buf1 );
6995                 }
6996
6997                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6998                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6999                     DisplayMove(currentMove - 1);
7000                 }
7001                 return;
7002
7003             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7004                 /* crafty (9.25+) says "(only move) <move>"
7005                  * if there is only 1 legal move
7006                  */
7007                 sscanf(p, "(only move) %s", buf1);
7008                 sprintf(thinkOutput, "%s (only move)", buf1);
7009                 sprintf(programStats.movelist, "%s (only move)", buf1);
7010                 programStats.depth = 1;
7011                 programStats.nr_moves = 1;
7012                 programStats.moves_left = 1;
7013                 programStats.nodes = 1;
7014                 programStats.time = 1;
7015                 programStats.got_only_move = 1;
7016
7017                 /* Not really, but we also use this member to
7018                    mean "line isn't going to change" (Crafty
7019                    isn't searching, so stats won't change) */
7020                 programStats.line_is_book = 1;
7021
7022                 SendProgramStatsToFrontend( cps, &programStats );
7023                 
7024                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7025                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7026                     DisplayMove(currentMove - 1);
7027                 }
7028                 return;
7029             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7030                               &time, &nodes, &plylev, &mvleft,
7031                               &mvtot, mvname) >= 5) {
7032                 /* The stat01: line is from Crafty (9.29+) in response
7033                    to the "." command */
7034                 programStats.seen_stat = 1;
7035                 cps->maybeThinking = TRUE;
7036
7037                 if (programStats.got_only_move || !appData.periodicUpdates)
7038                   return;
7039
7040                 programStats.depth = plylev;
7041                 programStats.time = time;
7042                 programStats.nodes = nodes;
7043                 programStats.moves_left = mvleft;
7044                 programStats.nr_moves = mvtot;
7045                 strcpy(programStats.move_name, mvname);
7046                 programStats.ok_to_send = 1;
7047                 programStats.movelist[0] = '\0';
7048
7049                 SendProgramStatsToFrontend( cps, &programStats );
7050
7051                 return;
7052
7053             } else if (strncmp(message,"++",2) == 0) {
7054                 /* Crafty 9.29+ outputs this */
7055                 programStats.got_fail = 2;
7056                 return;
7057
7058             } else if (strncmp(message,"--",2) == 0) {
7059                 /* Crafty 9.29+ outputs this */
7060                 programStats.got_fail = 1;
7061                 return;
7062
7063             } else if (thinkOutput[0] != NULLCHAR &&
7064                        strncmp(message, "    ", 4) == 0) {
7065                 unsigned message_len;
7066
7067                 p = message;
7068                 while (*p && *p == ' ') p++;
7069
7070                 message_len = strlen( p );
7071
7072                 /* [AS] Avoid buffer overflow */
7073                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7074                     strcat(thinkOutput, " ");
7075                     strcat(thinkOutput, p);
7076                 }
7077
7078                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7079                     strcat(programStats.movelist, " ");
7080                     strcat(programStats.movelist, p);
7081                 }
7082
7083                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7084                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7085                     DisplayMove(currentMove - 1);
7086                 }
7087                 return;
7088             }
7089         }
7090         else {
7091             buf1[0] = NULLCHAR;
7092
7093             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7094                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7095             {
7096                 ChessProgramStats cpstats;
7097
7098                 if (plyext != ' ' && plyext != '\t') {
7099                     time *= 100;
7100                 }
7101
7102                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7103                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7104                     curscore = -curscore;
7105                 }
7106
7107                 cpstats.depth = plylev;
7108                 cpstats.nodes = nodes;
7109                 cpstats.time = time;
7110                 cpstats.score = curscore;
7111                 cpstats.got_only_move = 0;
7112                 cpstats.movelist[0] = '\0';
7113
7114                 if (buf1[0] != NULLCHAR) {
7115                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7116                 }
7117
7118                 cpstats.ok_to_send = 0;
7119                 cpstats.line_is_book = 0;
7120                 cpstats.nr_moves = 0;
7121                 cpstats.moves_left = 0;
7122
7123                 SendProgramStatsToFrontend( cps, &cpstats );
7124             }
7125         }
7126     }
7127 }
7128
7129
7130 /* Parse a game score from the character string "game", and
7131    record it as the history of the current game.  The game
7132    score is NOT assumed to start from the standard position. 
7133    The display is not updated in any way.
7134    */
7135 void
7136 ParseGameHistory(game)
7137      char *game;
7138 {
7139     ChessMove moveType;
7140     int fromX, fromY, toX, toY, boardIndex;
7141     char promoChar;
7142     char *p, *q;
7143     char buf[MSG_SIZ];
7144
7145     if (appData.debugMode)
7146       fprintf(debugFP, "Parsing game history: %s\n", game);
7147
7148     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7149     gameInfo.site = StrSave(appData.icsHost);
7150     gameInfo.date = PGNDate();
7151     gameInfo.round = StrSave("-");
7152
7153     /* Parse out names of players */
7154     while (*game == ' ') game++;
7155     p = buf;
7156     while (*game != ' ') *p++ = *game++;
7157     *p = NULLCHAR;
7158     gameInfo.white = StrSave(buf);
7159     while (*game == ' ') game++;
7160     p = buf;
7161     while (*game != ' ' && *game != '\n') *p++ = *game++;
7162     *p = NULLCHAR;
7163     gameInfo.black = StrSave(buf);
7164
7165     /* Parse moves */
7166     boardIndex = blackPlaysFirst ? 1 : 0;
7167     yynewstr(game);
7168     for (;;) {
7169         yyboardindex = boardIndex;
7170         moveType = (ChessMove) yylex();
7171         switch (moveType) {
7172           case IllegalMove:             /* maybe suicide chess, etc. */
7173   if (appData.debugMode) {
7174     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7175     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7176     setbuf(debugFP, NULL);
7177   }
7178           case WhitePromotionChancellor:
7179           case BlackPromotionChancellor:
7180           case WhitePromotionArchbishop:
7181           case BlackPromotionArchbishop:
7182           case WhitePromotionQueen:
7183           case BlackPromotionQueen:
7184           case WhitePromotionRook:
7185           case BlackPromotionRook:
7186           case WhitePromotionBishop:
7187           case BlackPromotionBishop:
7188           case WhitePromotionKnight:
7189           case BlackPromotionKnight:
7190           case WhitePromotionKing:
7191           case BlackPromotionKing:
7192           case NormalMove:
7193           case WhiteCapturesEnPassant:
7194           case BlackCapturesEnPassant:
7195           case WhiteKingSideCastle:
7196           case WhiteQueenSideCastle:
7197           case BlackKingSideCastle:
7198           case BlackQueenSideCastle:
7199           case WhiteKingSideCastleWild:
7200           case WhiteQueenSideCastleWild:
7201           case BlackKingSideCastleWild:
7202           case BlackQueenSideCastleWild:
7203           /* PUSH Fabien */
7204           case WhiteHSideCastleFR:
7205           case WhiteASideCastleFR:
7206           case BlackHSideCastleFR:
7207           case BlackASideCastleFR:
7208           /* POP Fabien */
7209             fromX = currentMoveString[0] - AAA;
7210             fromY = currentMoveString[1] - ONE;
7211             toX = currentMoveString[2] - AAA;
7212             toY = currentMoveString[3] - ONE;
7213             promoChar = currentMoveString[4];
7214             break;
7215           case WhiteDrop:
7216           case BlackDrop:
7217             fromX = moveType == WhiteDrop ?
7218               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7219             (int) CharToPiece(ToLower(currentMoveString[0]));
7220             fromY = DROP_RANK;
7221             toX = currentMoveString[2] - AAA;
7222             toY = currentMoveString[3] - ONE;
7223             promoChar = NULLCHAR;
7224             break;
7225           case AmbiguousMove:
7226             /* bug? */
7227             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7228   if (appData.debugMode) {
7229     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7230     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7231     setbuf(debugFP, NULL);
7232   }
7233             DisplayError(buf, 0);
7234             return;
7235           case ImpossibleMove:
7236             /* bug? */
7237             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7238   if (appData.debugMode) {
7239     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7241     setbuf(debugFP, NULL);
7242   }
7243             DisplayError(buf, 0);
7244             return;
7245           case (ChessMove) 0:   /* end of file */
7246             if (boardIndex < backwardMostMove) {
7247                 /* Oops, gap.  How did that happen? */
7248                 DisplayError(_("Gap in move list"), 0);
7249                 return;
7250             }
7251             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7252             if (boardIndex > forwardMostMove) {
7253                 forwardMostMove = boardIndex;
7254             }
7255             return;
7256           case ElapsedTime:
7257             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7258                 strcat(parseList[boardIndex-1], " ");
7259                 strcat(parseList[boardIndex-1], yy_text);
7260             }
7261             continue;
7262           case Comment:
7263           case PGNTag:
7264           case NAG:
7265           default:
7266             /* ignore */
7267             continue;
7268           case WhiteWins:
7269           case BlackWins:
7270           case GameIsDrawn:
7271           case GameUnfinished:
7272             if (gameMode == IcsExamining) {
7273                 if (boardIndex < backwardMostMove) {
7274                     /* Oops, gap.  How did that happen? */
7275                     return;
7276                 }
7277                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7278                 return;
7279             }
7280             gameInfo.result = moveType;
7281             p = strchr(yy_text, '{');
7282             if (p == NULL) p = strchr(yy_text, '(');
7283             if (p == NULL) {
7284                 p = yy_text;
7285                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7286             } else {
7287                 q = strchr(p, *p == '{' ? '}' : ')');
7288                 if (q != NULL) *q = NULLCHAR;
7289                 p++;
7290             }
7291             gameInfo.resultDetails = StrSave(p);
7292             continue;
7293         }
7294         if (boardIndex >= forwardMostMove &&
7295             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7296             backwardMostMove = blackPlaysFirst ? 1 : 0;
7297             return;
7298         }
7299         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7300                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7301                                  parseList[boardIndex]);
7302         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7303         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7304         /* currentMoveString is set as a side-effect of yylex */
7305         strcpy(moveList[boardIndex], currentMoveString);
7306         strcat(moveList[boardIndex], "\n");
7307         boardIndex++;
7308         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7309                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7310         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7311                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7312           case MT_NONE:
7313           case MT_STALEMATE:
7314           default:
7315             break;
7316           case MT_CHECK:
7317             if(gameInfo.variant != VariantShogi)
7318                 strcat(parseList[boardIndex - 1], "+");
7319             break;
7320           case MT_CHECKMATE:
7321           case MT_STAINMATE:
7322             strcat(parseList[boardIndex - 1], "#");
7323             break;
7324         }
7325     }
7326 }
7327
7328
7329 /* Apply a move to the given board  */
7330 void
7331 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7332      int fromX, fromY, toX, toY;
7333      int promoChar;
7334      Board board;
7335      char *castling;
7336      char *ep;
7337 {
7338   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7339
7340     /* [HGM] compute & store e.p. status and castling rights for new position */
7341     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7342     { int i;
7343
7344       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7345       oldEP = *ep;
7346       *ep = EP_NONE;
7347
7348       if( board[toY][toX] != EmptySquare ) 
7349            *ep = EP_CAPTURE;  
7350
7351       if( board[fromY][fromX] == WhitePawn ) {
7352            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7353                *ep = EP_PAWN_MOVE;
7354            if( toY-fromY==2) {
7355                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7356                         gameInfo.variant != VariantBerolina || toX < fromX)
7357                       *ep = toX | berolina;
7358                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7359                         gameInfo.variant != VariantBerolina || toX > fromX) 
7360                       *ep = toX;
7361            }
7362       } else 
7363       if( board[fromY][fromX] == BlackPawn ) {
7364            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7365                *ep = EP_PAWN_MOVE; 
7366            if( toY-fromY== -2) {
7367                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7368                         gameInfo.variant != VariantBerolina || toX < fromX)
7369                       *ep = toX | berolina;
7370                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7371                         gameInfo.variant != VariantBerolina || toX > fromX) 
7372                       *ep = toX;
7373            }
7374        }
7375
7376        for(i=0; i<nrCastlingRights; i++) {
7377            if(castling[i] == fromX && castlingRank[i] == fromY ||
7378               castling[i] == toX   && castlingRank[i] == toY   
7379              ) castling[i] = -1; // revoke for moved or captured piece
7380        }
7381
7382     }
7383
7384   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7385   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7386        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7387          
7388   if (fromX == toX && fromY == toY) return;
7389
7390   if (fromY == DROP_RANK) {
7391         /* must be first */
7392         piece = board[toY][toX] = (ChessSquare) fromX;
7393   } else {
7394      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7395      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7396      if(gameInfo.variant == VariantKnightmate)
7397          king += (int) WhiteUnicorn - (int) WhiteKing;
7398
7399     /* Code added by Tord: */
7400     /* FRC castling assumed when king captures friendly rook. */
7401     if (board[fromY][fromX] == WhiteKing &&
7402              board[toY][toX] == WhiteRook) {
7403       board[fromY][fromX] = EmptySquare;
7404       board[toY][toX] = EmptySquare;
7405       if(toX > fromX) {
7406         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7407       } else {
7408         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7409       }
7410     } else if (board[fromY][fromX] == BlackKing &&
7411                board[toY][toX] == BlackRook) {
7412       board[fromY][fromX] = EmptySquare;
7413       board[toY][toX] = EmptySquare;
7414       if(toX > fromX) {
7415         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7416       } else {
7417         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7418       }
7419     /* End of code added by Tord */
7420
7421     } else if (board[fromY][fromX] == king
7422         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7423         && toY == fromY && toX > fromX+1) {
7424         board[fromY][fromX] = EmptySquare;
7425         board[toY][toX] = king;
7426         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7427         board[fromY][BOARD_RGHT-1] = EmptySquare;
7428     } else if (board[fromY][fromX] == king
7429         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7430                && toY == fromY && toX < fromX-1) {
7431         board[fromY][fromX] = EmptySquare;
7432         board[toY][toX] = king;
7433         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7434         board[fromY][BOARD_LEFT] = EmptySquare;
7435     } else if (board[fromY][fromX] == WhitePawn
7436                && toY == BOARD_HEIGHT-1
7437                && gameInfo.variant != VariantXiangqi
7438                ) {
7439         /* white pawn promotion */
7440         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7441         if (board[toY][toX] == EmptySquare) {
7442             board[toY][toX] = WhiteQueen;
7443         }
7444         if(gameInfo.variant==VariantBughouse ||
7445            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7446             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7447         board[fromY][fromX] = EmptySquare;
7448     } else if ((fromY == BOARD_HEIGHT-4)
7449                && (toX != fromX)
7450                && gameInfo.variant != VariantXiangqi
7451                && gameInfo.variant != VariantBerolina
7452                && (board[fromY][fromX] == WhitePawn)
7453                && (board[toY][toX] == EmptySquare)) {
7454         board[fromY][fromX] = EmptySquare;
7455         board[toY][toX] = WhitePawn;
7456         captured = board[toY - 1][toX];
7457         board[toY - 1][toX] = EmptySquare;
7458     } else if ((fromY == BOARD_HEIGHT-4)
7459                && (toX == fromX)
7460                && gameInfo.variant == VariantBerolina
7461                && (board[fromY][fromX] == WhitePawn)
7462                && (board[toY][toX] == EmptySquare)) {
7463         board[fromY][fromX] = EmptySquare;
7464         board[toY][toX] = WhitePawn;
7465         if(oldEP & EP_BEROLIN_A) {
7466                 captured = board[fromY][fromX-1];
7467                 board[fromY][fromX-1] = EmptySquare;
7468         }else{  captured = board[fromY][fromX+1];
7469                 board[fromY][fromX+1] = EmptySquare;
7470         }
7471     } else if (board[fromY][fromX] == king
7472         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7473                && toY == fromY && toX > fromX+1) {
7474         board[fromY][fromX] = EmptySquare;
7475         board[toY][toX] = king;
7476         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7477         board[fromY][BOARD_RGHT-1] = EmptySquare;
7478     } else if (board[fromY][fromX] == king
7479         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7480                && toY == fromY && toX < fromX-1) {
7481         board[fromY][fromX] = EmptySquare;
7482         board[toY][toX] = king;
7483         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7484         board[fromY][BOARD_LEFT] = EmptySquare;
7485     } else if (fromY == 7 && fromX == 3
7486                && board[fromY][fromX] == BlackKing
7487                && toY == 7 && toX == 5) {
7488         board[fromY][fromX] = EmptySquare;
7489         board[toY][toX] = BlackKing;
7490         board[fromY][7] = EmptySquare;
7491         board[toY][4] = BlackRook;
7492     } else if (fromY == 7 && fromX == 3
7493                && board[fromY][fromX] == BlackKing
7494                && toY == 7 && toX == 1) {
7495         board[fromY][fromX] = EmptySquare;
7496         board[toY][toX] = BlackKing;
7497         board[fromY][0] = EmptySquare;
7498         board[toY][2] = BlackRook;
7499     } else if (board[fromY][fromX] == BlackPawn
7500                && toY == 0
7501                && gameInfo.variant != VariantXiangqi
7502                ) {
7503         /* black pawn promotion */
7504         board[0][toX] = CharToPiece(ToLower(promoChar));
7505         if (board[0][toX] == EmptySquare) {
7506             board[0][toX] = BlackQueen;
7507         }
7508         if(gameInfo.variant==VariantBughouse ||
7509            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7510             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7511         board[fromY][fromX] = EmptySquare;
7512     } else if ((fromY == 3)
7513                && (toX != fromX)
7514                && gameInfo.variant != VariantXiangqi
7515                && gameInfo.variant != VariantBerolina
7516                && (board[fromY][fromX] == BlackPawn)
7517                && (board[toY][toX] == EmptySquare)) {
7518         board[fromY][fromX] = EmptySquare;
7519         board[toY][toX] = BlackPawn;
7520         captured = board[toY + 1][toX];
7521         board[toY + 1][toX] = EmptySquare;
7522     } else if ((fromY == 3)
7523                && (toX == fromX)
7524                && gameInfo.variant == VariantBerolina
7525                && (board[fromY][fromX] == BlackPawn)
7526                && (board[toY][toX] == EmptySquare)) {
7527         board[fromY][fromX] = EmptySquare;
7528         board[toY][toX] = BlackPawn;
7529         if(oldEP & EP_BEROLIN_A) {
7530                 captured = board[fromY][fromX-1];
7531                 board[fromY][fromX-1] = EmptySquare;
7532         }else{  captured = board[fromY][fromX+1];
7533                 board[fromY][fromX+1] = EmptySquare;
7534         }
7535     } else {
7536         board[toY][toX] = board[fromY][fromX];
7537         board[fromY][fromX] = EmptySquare;
7538     }
7539
7540     /* [HGM] now we promote for Shogi, if needed */
7541     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7542         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7543   }
7544
7545     if (gameInfo.holdingsWidth != 0) {
7546
7547       /* !!A lot more code needs to be written to support holdings  */
7548       /* [HGM] OK, so I have written it. Holdings are stored in the */
7549       /* penultimate board files, so they are automaticlly stored   */
7550       /* in the game history.                                       */
7551       if (fromY == DROP_RANK) {
7552         /* Delete from holdings, by decreasing count */
7553         /* and erasing image if necessary            */
7554         p = (int) fromX;
7555         if(p < (int) BlackPawn) { /* white drop */
7556              p -= (int)WhitePawn;
7557                  p = PieceToNumber((ChessSquare)p);
7558              if(p >= gameInfo.holdingsSize) p = 0;
7559              if(--board[p][BOARD_WIDTH-2] <= 0)
7560                   board[p][BOARD_WIDTH-1] = EmptySquare;
7561              if((int)board[p][BOARD_WIDTH-2] < 0)
7562                         board[p][BOARD_WIDTH-2] = 0;
7563         } else {                  /* black drop */
7564              p -= (int)BlackPawn;
7565                  p = PieceToNumber((ChessSquare)p);
7566              if(p >= gameInfo.holdingsSize) p = 0;
7567              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7568                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7569              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7570                         board[BOARD_HEIGHT-1-p][1] = 0;
7571         }
7572       }
7573       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7574           && gameInfo.variant != VariantBughouse        ) {
7575         /* [HGM] holdings: Add to holdings, if holdings exist */
7576         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7577                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7578                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7579         }
7580         p = (int) captured;
7581         if (p >= (int) BlackPawn) {
7582           p -= (int)BlackPawn;
7583           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7584                   /* in Shogi restore piece to its original  first */
7585                   captured = (ChessSquare) (DEMOTED captured);
7586                   p = DEMOTED p;
7587           }
7588           p = PieceToNumber((ChessSquare)p);
7589           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7590           board[p][BOARD_WIDTH-2]++;
7591           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7592         } else {
7593           p -= (int)WhitePawn;
7594           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7595                   captured = (ChessSquare) (DEMOTED captured);
7596                   p = DEMOTED p;
7597           }
7598           p = PieceToNumber((ChessSquare)p);
7599           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7600           board[BOARD_HEIGHT-1-p][1]++;
7601           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7602         }
7603       }
7604     } else if (gameInfo.variant == VariantAtomic) {
7605       if (captured != EmptySquare) {
7606         int y, x;
7607         for (y = toY-1; y <= toY+1; y++) {
7608           for (x = toX-1; x <= toX+1; x++) {
7609             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7610                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7611               board[y][x] = EmptySquare;
7612             }
7613           }
7614         }
7615         board[toY][toX] = EmptySquare;
7616       }
7617     }
7618     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7619         /* [HGM] Shogi promotions */
7620         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7621     }
7622
7623     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7624                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7625         // [HGM] superchess: take promotion piece out of holdings
7626         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7627         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7628             if(!--board[k][BOARD_WIDTH-2])
7629                 board[k][BOARD_WIDTH-1] = EmptySquare;
7630         } else {
7631             if(!--board[BOARD_HEIGHT-1-k][1])
7632                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7633         }
7634     }
7635
7636 }
7637
7638 /* Updates forwardMostMove */
7639 void
7640 MakeMove(fromX, fromY, toX, toY, promoChar)
7641      int fromX, fromY, toX, toY;
7642      int promoChar;
7643 {
7644 //    forwardMostMove++; // [HGM] bare: moved downstream
7645
7646     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7647         int timeLeft; static int lastLoadFlag=0; int king, piece;
7648         piece = boards[forwardMostMove][fromY][fromX];
7649         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7650         if(gameInfo.variant == VariantKnightmate)
7651             king += (int) WhiteUnicorn - (int) WhiteKing;
7652         if(forwardMostMove == 0) {
7653             if(blackPlaysFirst) 
7654                 fprintf(serverMoves, "%s;", second.tidy);
7655             fprintf(serverMoves, "%s;", first.tidy);
7656             if(!blackPlaysFirst) 
7657                 fprintf(serverMoves, "%s;", second.tidy);
7658         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7659         lastLoadFlag = loadFlag;
7660         // print base move
7661         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7662         // print castling suffix
7663         if( toY == fromY && piece == king ) {
7664             if(toX-fromX > 1)
7665                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7666             if(fromX-toX >1)
7667                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7668         }
7669         // e.p. suffix
7670         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7671              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7672              boards[forwardMostMove][toY][toX] == EmptySquare
7673              && fromX != toX )
7674                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7675         // promotion suffix
7676         if(promoChar != NULLCHAR)
7677                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7678         if(!loadFlag) {
7679             fprintf(serverMoves, "/%d/%d",
7680                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7681             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7682             else                      timeLeft = blackTimeRemaining/1000;
7683             fprintf(serverMoves, "/%d", timeLeft);
7684         }
7685         fflush(serverMoves);
7686     }
7687
7688     if (forwardMostMove+1 >= MAX_MOVES) {
7689       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7690                         0, 1);
7691       return;
7692     }
7693     if (commentList[forwardMostMove+1] != NULL) {
7694         free(commentList[forwardMostMove+1]);
7695         commentList[forwardMostMove+1] = NULL;
7696     }
7697     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7698     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7699     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7700                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7701     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7702     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7703     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7704     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7705     gameInfo.result = GameUnfinished;
7706     if (gameInfo.resultDetails != NULL) {
7707         free(gameInfo.resultDetails);
7708         gameInfo.resultDetails = NULL;
7709     }
7710     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7711                               moveList[forwardMostMove - 1]);
7712     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7713                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7714                              fromY, fromX, toY, toX, promoChar,
7715                              parseList[forwardMostMove - 1]);
7716     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7717                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7718                             castlingRights[forwardMostMove]) ) {
7719       case MT_NONE:
7720       case MT_STALEMATE:
7721       default:
7722         break;
7723       case MT_CHECK:
7724         if(gameInfo.variant != VariantShogi)
7725             strcat(parseList[forwardMostMove - 1], "+");
7726         break;
7727       case MT_CHECKMATE:
7728       case MT_STAINMATE:
7729         strcat(parseList[forwardMostMove - 1], "#");
7730         break;
7731     }
7732     if (appData.debugMode) {
7733         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7734     }
7735
7736 }
7737
7738 /* Updates currentMove if not pausing */
7739 void
7740 ShowMove(fromX, fromY, toX, toY)
7741 {
7742     int instant = (gameMode == PlayFromGameFile) ?
7743         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7744     if(appData.noGUI) return;
7745     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7746         if (!instant) {
7747             if (forwardMostMove == currentMove + 1) {
7748                 AnimateMove(boards[forwardMostMove - 1],
7749                             fromX, fromY, toX, toY);
7750             }
7751             if (appData.highlightLastMove) {
7752                 SetHighlights(fromX, fromY, toX, toY);
7753             }
7754         }
7755         currentMove = forwardMostMove;
7756     }
7757
7758     if (instant) return;
7759
7760     DisplayMove(currentMove - 1);
7761     DrawPosition(FALSE, boards[currentMove]);
7762     DisplayBothClocks();
7763     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7764 }
7765
7766 void SendEgtPath(ChessProgramState *cps)
7767 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7768         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7769
7770         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7771
7772         while(*p) {
7773             char c, *q = name+1, *r, *s;
7774
7775             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7776             while(*p && *p != ',') *q++ = *p++;
7777             *q++ = ':'; *q = 0;
7778             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7779                 strcmp(name, ",nalimov:") == 0 ) {
7780                 // take nalimov path from the menu-changeable option first, if it is defined
7781                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7782                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7783             } else
7784             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7785                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7786                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7787                 s = r = StrStr(s, ":") + 1; // beginning of path info
7788                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7789                 c = *r; *r = 0;             // temporarily null-terminate path info
7790                     *--q = 0;               // strip of trailig ':' from name
7791                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7792                 *r = c;
7793                 SendToProgram(buf,cps);     // send egtbpath command for this format
7794             }
7795             if(*p == ',') p++; // read away comma to position for next format name
7796         }
7797 }
7798
7799 void
7800 InitChessProgram(cps, setup)
7801      ChessProgramState *cps;
7802      int setup; /* [HGM] needed to setup FRC opening position */
7803 {
7804     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7805     if (appData.noChessProgram) return;
7806     hintRequested = FALSE;
7807     bookRequested = FALSE;
7808
7809     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7810     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7811     if(cps->memSize) { /* [HGM] memory */
7812         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7813         SendToProgram(buf, cps);
7814     }
7815     SendEgtPath(cps); /* [HGM] EGT */
7816     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7817         sprintf(buf, "cores %d\n", appData.smpCores);
7818         SendToProgram(buf, cps);
7819     }
7820
7821     SendToProgram(cps->initString, cps);
7822     if (gameInfo.variant != VariantNormal &&
7823         gameInfo.variant != VariantLoadable
7824         /* [HGM] also send variant if board size non-standard */
7825         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7826                                             ) {
7827       char *v = VariantName(gameInfo.variant);
7828       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7829         /* [HGM] in protocol 1 we have to assume all variants valid */
7830         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7831         DisplayFatalError(buf, 0, 1);
7832         return;
7833       }
7834
7835       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7836       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7837       if( gameInfo.variant == VariantXiangqi )
7838            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7839       if( gameInfo.variant == VariantShogi )
7840            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7841       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7842            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7843       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7844                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7845            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7846       if( gameInfo.variant == VariantCourier )
7847            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7848       if( gameInfo.variant == VariantSuper )
7849            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7850       if( gameInfo.variant == VariantGreat )
7851            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7852
7853       if(overruled) {
7854            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7855                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7856            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7857            if(StrStr(cps->variants, b) == NULL) { 
7858                // specific sized variant not known, check if general sizing allowed
7859                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7860                    if(StrStr(cps->variants, "boardsize") == NULL) {
7861                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7862                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7863                        DisplayFatalError(buf, 0, 1);
7864                        return;
7865                    }
7866                    /* [HGM] here we really should compare with the maximum supported board size */
7867                }
7868            }
7869       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7870       sprintf(buf, "variant %s\n", b);
7871       SendToProgram(buf, cps);
7872     }
7873     currentlyInitializedVariant = gameInfo.variant;
7874
7875     /* [HGM] send opening position in FRC to first engine */
7876     if(setup) {
7877           SendToProgram("force\n", cps);
7878           SendBoard(cps, 0);
7879           /* engine is now in force mode! Set flag to wake it up after first move. */
7880           setboardSpoiledMachineBlack = 1;
7881     }
7882
7883     if (cps->sendICS) {
7884       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7885       SendToProgram(buf, cps);
7886     }
7887     cps->maybeThinking = FALSE;
7888     cps->offeredDraw = 0;
7889     if (!appData.icsActive) {
7890         SendTimeControl(cps, movesPerSession, timeControl,
7891                         timeIncrement, appData.searchDepth,
7892                         searchTime);
7893     }
7894     if (appData.showThinking 
7895         // [HGM] thinking: four options require thinking output to be sent
7896         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7897                                 ) {
7898         SendToProgram("post\n", cps);
7899     }
7900     SendToProgram("hard\n", cps);
7901     if (!appData.ponderNextMove) {
7902         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7903            it without being sure what state we are in first.  "hard"
7904            is not a toggle, so that one is OK.
7905          */
7906         SendToProgram("easy\n", cps);
7907     }
7908     if (cps->usePing) {
7909       sprintf(buf, "ping %d\n", ++cps->lastPing);
7910       SendToProgram(buf, cps);
7911     }
7912     cps->initDone = TRUE;
7913 }   
7914
7915
7916 void
7917 StartChessProgram(cps)
7918      ChessProgramState *cps;
7919 {
7920     char buf[MSG_SIZ];
7921     int err;
7922
7923     if (appData.noChessProgram) return;
7924     cps->initDone = FALSE;
7925
7926     if (strcmp(cps->host, "localhost") == 0) {
7927         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7928     } else if (*appData.remoteShell == NULLCHAR) {
7929         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7930     } else {
7931         if (*appData.remoteUser == NULLCHAR) {
7932           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7933                     cps->program);
7934         } else {
7935           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7936                     cps->host, appData.remoteUser, cps->program);
7937         }
7938         err = StartChildProcess(buf, "", &cps->pr);
7939     }
7940     
7941     if (err != 0) {
7942         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7943         DisplayFatalError(buf, err, 1);
7944         cps->pr = NoProc;
7945         cps->isr = NULL;
7946         return;
7947     }
7948     
7949     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7950     if (cps->protocolVersion > 1) {
7951       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7952       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7953       cps->comboCnt = 0;  //                and values of combo boxes
7954       SendToProgram(buf, cps);
7955     } else {
7956       SendToProgram("xboard\n", cps);
7957     }
7958 }
7959
7960
7961 void
7962 TwoMachinesEventIfReady P((void))
7963 {
7964   if (first.lastPing != first.lastPong) {
7965     DisplayMessage("", _("Waiting for first chess program"));
7966     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7967     return;
7968   }
7969   if (second.lastPing != second.lastPong) {
7970     DisplayMessage("", _("Waiting for second chess program"));
7971     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7972     return;
7973   }
7974   ThawUI();
7975   TwoMachinesEvent();
7976 }
7977
7978 void
7979 NextMatchGame P((void))
7980 {
7981     int index; /* [HGM] autoinc: step load index during match */
7982     Reset(FALSE, TRUE);
7983     if (*appData.loadGameFile != NULLCHAR) {
7984         index = appData.loadGameIndex;
7985         if(index < 0) { // [HGM] autoinc
7986             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7987             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7988         } 
7989         LoadGameFromFile(appData.loadGameFile,
7990                          index,
7991                          appData.loadGameFile, FALSE);
7992     } else if (*appData.loadPositionFile != NULLCHAR) {
7993         index = appData.loadPositionIndex;
7994         if(index < 0) { // [HGM] autoinc
7995             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7996             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7997         } 
7998         LoadPositionFromFile(appData.loadPositionFile,
7999                              index,
8000                              appData.loadPositionFile);
8001     }
8002     TwoMachinesEventIfReady();
8003 }
8004
8005 void UserAdjudicationEvent( int result )
8006 {
8007     ChessMove gameResult = GameIsDrawn;
8008
8009     if( result > 0 ) {
8010         gameResult = WhiteWins;
8011     }
8012     else if( result < 0 ) {
8013         gameResult = BlackWins;
8014     }
8015
8016     if( gameMode == TwoMachinesPlay ) {
8017         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8018     }
8019 }
8020
8021
8022 // [HGM] save: calculate checksum of game to make games easily identifiable
8023 int StringCheckSum(char *s)
8024 {
8025         int i = 0;
8026         if(s==NULL) return 0;
8027         while(*s) i = i*259 + *s++;
8028         return i;
8029 }
8030
8031 int GameCheckSum()
8032 {
8033         int i, sum=0;
8034         for(i=backwardMostMove; i<forwardMostMove; i++) {
8035                 sum += pvInfoList[i].depth;
8036                 sum += StringCheckSum(parseList[i]);
8037                 sum += StringCheckSum(commentList[i]);
8038                 sum *= 261;
8039         }
8040         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8041         return sum + StringCheckSum(commentList[i]);
8042 } // end of save patch
8043
8044 void
8045 GameEnds(result, resultDetails, whosays)
8046      ChessMove result;
8047      char *resultDetails;
8048      int whosays;
8049 {
8050     GameMode nextGameMode;
8051     int isIcsGame;
8052     char buf[MSG_SIZ];
8053
8054     if(endingGame) return; /* [HGM] crash: forbid recursion */
8055     endingGame = 1;
8056
8057     if (appData.debugMode) {
8058       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8059               result, resultDetails ? resultDetails : "(null)", whosays);
8060     }
8061
8062     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8063         /* If we are playing on ICS, the server decides when the
8064            game is over, but the engine can offer to draw, claim 
8065            a draw, or resign. 
8066          */
8067 #if ZIPPY
8068         if (appData.zippyPlay && first.initDone) {
8069             if (result == GameIsDrawn) {
8070                 /* In case draw still needs to be claimed */
8071                 SendToICS(ics_prefix);
8072                 SendToICS("draw\n");
8073             } else if (StrCaseStr(resultDetails, "resign")) {
8074                 SendToICS(ics_prefix);
8075                 SendToICS("resign\n");
8076             }
8077         }
8078 #endif
8079         endingGame = 0; /* [HGM] crash */
8080         return;
8081     }
8082
8083     /* If we're loading the game from a file, stop */
8084     if (whosays == GE_FILE) {
8085       (void) StopLoadGameTimer();
8086       gameFileFP = NULL;
8087     }
8088
8089     /* Cancel draw offers */
8090     first.offeredDraw = second.offeredDraw = 0;
8091
8092     /* If this is an ICS game, only ICS can really say it's done;
8093        if not, anyone can. */
8094     isIcsGame = (gameMode == IcsPlayingWhite || 
8095                  gameMode == IcsPlayingBlack || 
8096                  gameMode == IcsObserving    || 
8097                  gameMode == IcsExamining);
8098
8099     if (!isIcsGame || whosays == GE_ICS) {
8100         /* OK -- not an ICS game, or ICS said it was done */
8101         StopClocks();
8102         if (!isIcsGame && !appData.noChessProgram) 
8103           SetUserThinkingEnables();
8104     
8105         /* [HGM] if a machine claims the game end we verify this claim */
8106         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8107             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8108                 char claimer;
8109                 ChessMove trueResult = (ChessMove) -1;
8110
8111                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8112                                             first.twoMachinesColor[0] :
8113                                             second.twoMachinesColor[0] ;
8114
8115                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8116                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8117                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8118                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8119                 } else
8120                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8121                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8122                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8123                 } else
8124                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8125                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8126                 }
8127
8128                 // now verify win claims, but not in drop games, as we don't understand those yet
8129                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8130                                                  || gameInfo.variant == VariantGreat) &&
8131                     (result == WhiteWins && claimer == 'w' ||
8132                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8133                       if (appData.debugMode) {
8134                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8135                                 result, epStatus[forwardMostMove], forwardMostMove);
8136                       }
8137                       if(result != trueResult) {
8138                               sprintf(buf, "False win claim: '%s'", resultDetails);
8139                               result = claimer == 'w' ? BlackWins : WhiteWins;
8140                               resultDetails = buf;
8141                       }
8142                 } else
8143                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8144                     && (forwardMostMove <= backwardMostMove ||
8145                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8146                         (claimer=='b')==(forwardMostMove&1))
8147                                                                                   ) {
8148                       /* [HGM] verify: draws that were not flagged are false claims */
8149                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8150                       result = claimer == 'w' ? BlackWins : WhiteWins;
8151                       resultDetails = buf;
8152                 }
8153                 /* (Claiming a loss is accepted no questions asked!) */
8154             }
8155             /* [HGM] bare: don't allow bare King to win */
8156             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8157                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8158                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8159                && result != GameIsDrawn)
8160             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8161                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8162                         int p = (int)boards[forwardMostMove][i][j] - color;
8163                         if(p >= 0 && p <= (int)WhiteKing) k++;
8164                 }
8165                 if (appData.debugMode) {
8166                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8167                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8168                 }
8169                 if(k <= 1) {
8170                         result = GameIsDrawn;
8171                         sprintf(buf, "%s but bare king", resultDetails);
8172                         resultDetails = buf;
8173                 }
8174             }
8175         }
8176
8177
8178         if(serverMoves != NULL && !loadFlag) { char c = '=';
8179             if(result==WhiteWins) c = '+';
8180             if(result==BlackWins) c = '-';
8181             if(resultDetails != NULL)
8182                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8183         }
8184         if (resultDetails != NULL) {
8185             gameInfo.result = result;
8186             gameInfo.resultDetails = StrSave(resultDetails);
8187
8188             /* display last move only if game was not loaded from file */
8189             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8190                 DisplayMove(currentMove - 1);
8191     
8192             if (forwardMostMove != 0) {
8193                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8194                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8195                                                                 ) {
8196                     if (*appData.saveGameFile != NULLCHAR) {
8197                         SaveGameToFile(appData.saveGameFile, TRUE);
8198                     } else if (appData.autoSaveGames) {
8199                         AutoSaveGame();
8200                     }
8201                     if (*appData.savePositionFile != NULLCHAR) {
8202                         SavePositionToFile(appData.savePositionFile);
8203                     }
8204                 }
8205             }
8206
8207             /* Tell program how game ended in case it is learning */
8208             /* [HGM] Moved this to after saving the PGN, just in case */
8209             /* engine died and we got here through time loss. In that */
8210             /* case we will get a fatal error writing the pipe, which */
8211             /* would otherwise lose us the PGN.                       */
8212             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8213             /* output during GameEnds should never be fatal anymore   */
8214             if (gameMode == MachinePlaysWhite ||
8215                 gameMode == MachinePlaysBlack ||
8216                 gameMode == TwoMachinesPlay ||
8217                 gameMode == IcsPlayingWhite ||
8218                 gameMode == IcsPlayingBlack ||
8219                 gameMode == BeginningOfGame) {
8220                 char buf[MSG_SIZ];
8221                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8222                         resultDetails);
8223                 if (first.pr != NoProc) {
8224                     SendToProgram(buf, &first);
8225                 }
8226                 if (second.pr != NoProc &&
8227                     gameMode == TwoMachinesPlay) {
8228                     SendToProgram(buf, &second);
8229                 }
8230             }
8231         }
8232
8233         if (appData.icsActive) {
8234             if (appData.quietPlay &&
8235                 (gameMode == IcsPlayingWhite ||
8236                  gameMode == IcsPlayingBlack)) {
8237                 SendToICS(ics_prefix);
8238                 SendToICS("set shout 1\n");
8239             }
8240             nextGameMode = IcsIdle;
8241             ics_user_moved = FALSE;
8242             /* clean up premove.  It's ugly when the game has ended and the
8243              * premove highlights are still on the board.
8244              */
8245             if (gotPremove) {
8246               gotPremove = FALSE;
8247               ClearPremoveHighlights();
8248               DrawPosition(FALSE, boards[currentMove]);
8249             }
8250             if (whosays == GE_ICS) {
8251                 switch (result) {
8252                 case WhiteWins:
8253                     if (gameMode == IcsPlayingWhite)
8254                         PlayIcsWinSound();
8255                     else if(gameMode == IcsPlayingBlack)
8256                         PlayIcsLossSound();
8257                     break;
8258                 case BlackWins:
8259                     if (gameMode == IcsPlayingBlack)
8260                         PlayIcsWinSound();
8261                     else if(gameMode == IcsPlayingWhite)
8262                         PlayIcsLossSound();
8263                     break;
8264                 case GameIsDrawn:
8265                     PlayIcsDrawSound();
8266                     break;
8267                 default:
8268                     PlayIcsUnfinishedSound();
8269                 }
8270             }
8271         } else if (gameMode == EditGame ||
8272                    gameMode == PlayFromGameFile || 
8273                    gameMode == AnalyzeMode || 
8274                    gameMode == AnalyzeFile) {
8275             nextGameMode = gameMode;
8276         } else {
8277             nextGameMode = EndOfGame;
8278         }
8279         pausing = FALSE;
8280         ModeHighlight();
8281     } else {
8282         nextGameMode = gameMode;
8283     }
8284
8285     if (appData.noChessProgram) {
8286         gameMode = nextGameMode;
8287         ModeHighlight();
8288         endingGame = 0; /* [HGM] crash */
8289         return;
8290     }
8291
8292     if (first.reuse) {
8293         /* Put first chess program into idle state */
8294         if (first.pr != NoProc &&
8295             (gameMode == MachinePlaysWhite ||
8296              gameMode == MachinePlaysBlack ||
8297              gameMode == TwoMachinesPlay ||
8298              gameMode == IcsPlayingWhite ||
8299              gameMode == IcsPlayingBlack ||
8300              gameMode == BeginningOfGame)) {
8301             SendToProgram("force\n", &first);
8302             if (first.usePing) {
8303               char buf[MSG_SIZ];
8304               sprintf(buf, "ping %d\n", ++first.lastPing);
8305               SendToProgram(buf, &first);
8306             }
8307         }
8308     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8309         /* Kill off first chess program */
8310         if (first.isr != NULL)
8311           RemoveInputSource(first.isr);
8312         first.isr = NULL;
8313     
8314         if (first.pr != NoProc) {
8315             ExitAnalyzeMode();
8316             DoSleep( appData.delayBeforeQuit );
8317             SendToProgram("quit\n", &first);
8318             DoSleep( appData.delayAfterQuit );
8319             DestroyChildProcess(first.pr, first.useSigterm);
8320         }
8321         first.pr = NoProc;
8322     }
8323     if (second.reuse) {
8324         /* Put second chess program into idle state */
8325         if (second.pr != NoProc &&
8326             gameMode == TwoMachinesPlay) {
8327             SendToProgram("force\n", &second);
8328             if (second.usePing) {
8329               char buf[MSG_SIZ];
8330               sprintf(buf, "ping %d\n", ++second.lastPing);
8331               SendToProgram(buf, &second);
8332             }
8333         }
8334     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8335         /* Kill off second chess program */
8336         if (second.isr != NULL)
8337           RemoveInputSource(second.isr);
8338         second.isr = NULL;
8339     
8340         if (second.pr != NoProc) {
8341             DoSleep( appData.delayBeforeQuit );
8342             SendToProgram("quit\n", &second);
8343             DoSleep( appData.delayAfterQuit );
8344             DestroyChildProcess(second.pr, second.useSigterm);
8345         }
8346         second.pr = NoProc;
8347     }
8348
8349     if (matchMode && gameMode == TwoMachinesPlay) {
8350         switch (result) {
8351         case WhiteWins:
8352           if (first.twoMachinesColor[0] == 'w') {
8353             first.matchWins++;
8354           } else {
8355             second.matchWins++;
8356           }
8357           break;
8358         case BlackWins:
8359           if (first.twoMachinesColor[0] == 'b') {
8360             first.matchWins++;
8361           } else {
8362             second.matchWins++;
8363           }
8364           break;
8365         default:
8366           break;
8367         }
8368         if (matchGame < appData.matchGames) {
8369             char *tmp;
8370             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8371                 tmp = first.twoMachinesColor;
8372                 first.twoMachinesColor = second.twoMachinesColor;
8373                 second.twoMachinesColor = tmp;
8374             }
8375             gameMode = nextGameMode;
8376             matchGame++;
8377             if(appData.matchPause>10000 || appData.matchPause<10)
8378                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8379             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8380             endingGame = 0; /* [HGM] crash */
8381             return;
8382         } else {
8383             char buf[MSG_SIZ];
8384             gameMode = nextGameMode;
8385             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8386                     first.tidy, second.tidy,
8387                     first.matchWins, second.matchWins,
8388                     appData.matchGames - (first.matchWins + second.matchWins));
8389             DisplayFatalError(buf, 0, 0);
8390         }
8391     }
8392     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8393         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8394       ExitAnalyzeMode();
8395     gameMode = nextGameMode;
8396     ModeHighlight();
8397     endingGame = 0;  /* [HGM] crash */
8398 }
8399
8400 /* Assumes program was just initialized (initString sent).
8401    Leaves program in force mode. */
8402 void
8403 FeedMovesToProgram(cps, upto) 
8404      ChessProgramState *cps;
8405      int upto;
8406 {
8407     int i;
8408     
8409     if (appData.debugMode)
8410       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8411               startedFromSetupPosition ? "position and " : "",
8412               backwardMostMove, upto, cps->which);
8413     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8414         // [HGM] variantswitch: make engine aware of new variant
8415         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8416                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8417         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8418         SendToProgram(buf, cps);
8419         currentlyInitializedVariant = gameInfo.variant;
8420     }
8421     SendToProgram("force\n", cps);
8422     if (startedFromSetupPosition) {
8423         SendBoard(cps, backwardMostMove);
8424     if (appData.debugMode) {
8425         fprintf(debugFP, "feedMoves\n");
8426     }
8427     }
8428     for (i = backwardMostMove; i < upto; i++) {
8429         SendMoveToProgram(i, cps);
8430     }
8431 }
8432
8433
8434 void
8435 ResurrectChessProgram()
8436 {
8437      /* The chess program may have exited.
8438         If so, restart it and feed it all the moves made so far. */
8439
8440     if (appData.noChessProgram || first.pr != NoProc) return;
8441     
8442     StartChessProgram(&first);
8443     InitChessProgram(&first, FALSE);
8444     FeedMovesToProgram(&first, currentMove);
8445
8446     if (!first.sendTime) {
8447         /* can't tell gnuchess what its clock should read,
8448            so we bow to its notion. */
8449         ResetClocks();
8450         timeRemaining[0][currentMove] = whiteTimeRemaining;
8451         timeRemaining[1][currentMove] = blackTimeRemaining;
8452     }
8453
8454     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8455                 appData.icsEngineAnalyze) && first.analysisSupport) {
8456       SendToProgram("analyze\n", &first);
8457       first.analyzing = TRUE;
8458     }
8459 }
8460
8461 /*
8462  * Button procedures
8463  */
8464 void
8465 Reset(redraw, init)
8466      int redraw, init;
8467 {
8468     int i;
8469
8470     if (appData.debugMode) {
8471         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8472                 redraw, init, gameMode);
8473     }
8474     pausing = pauseExamInvalid = FALSE;
8475     startedFromSetupPosition = blackPlaysFirst = FALSE;
8476     firstMove = TRUE;
8477     whiteFlag = blackFlag = FALSE;
8478     userOfferedDraw = FALSE;
8479     hintRequested = bookRequested = FALSE;
8480     first.maybeThinking = FALSE;
8481     second.maybeThinking = FALSE;
8482     first.bookSuspend = FALSE; // [HGM] book
8483     second.bookSuspend = FALSE;
8484     thinkOutput[0] = NULLCHAR;
8485     lastHint[0] = NULLCHAR;
8486     ClearGameInfo(&gameInfo);
8487     gameInfo.variant = StringToVariant(appData.variant);
8488     ics_user_moved = ics_clock_paused = FALSE;
8489     ics_getting_history = H_FALSE;
8490     ics_gamenum = -1;
8491     white_holding[0] = black_holding[0] = NULLCHAR;
8492     ClearProgramStats();
8493     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8494     
8495     ResetFrontEnd();
8496     ClearHighlights();
8497     flipView = appData.flipView;
8498     ClearPremoveHighlights();
8499     gotPremove = FALSE;
8500     alarmSounded = FALSE;
8501
8502     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8503     if(appData.serverMovesName != NULL) {
8504         /* [HGM] prepare to make moves file for broadcasting */
8505         clock_t t = clock();
8506         if(serverMoves != NULL) fclose(serverMoves);
8507         serverMoves = fopen(appData.serverMovesName, "r");
8508         if(serverMoves != NULL) {
8509             fclose(serverMoves);
8510             /* delay 15 sec before overwriting, so all clients can see end */
8511             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8512         }
8513         serverMoves = fopen(appData.serverMovesName, "w");
8514     }
8515
8516     ExitAnalyzeMode();
8517     gameMode = BeginningOfGame;
8518     ModeHighlight();
8519     if(appData.icsActive) gameInfo.variant = VariantNormal;
8520     currentMove = forwardMostMove = backwardMostMove = 0;
8521     InitPosition(redraw);
8522     for (i = 0; i < MAX_MOVES; i++) {
8523         if (commentList[i] != NULL) {
8524             free(commentList[i]);
8525             commentList[i] = NULL;
8526         }
8527     }
8528     ResetClocks();
8529     timeRemaining[0][0] = whiteTimeRemaining;
8530     timeRemaining[1][0] = blackTimeRemaining;
8531     if (first.pr == NULL) {
8532         StartChessProgram(&first);
8533     }
8534     if (init) {
8535             InitChessProgram(&first, startedFromSetupPosition);
8536     }
8537     DisplayTitle("");
8538     DisplayMessage("", "");
8539     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8540     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8541 }
8542
8543 void
8544 AutoPlayGameLoop()
8545 {
8546     for (;;) {
8547         if (!AutoPlayOneMove())
8548           return;
8549         if (matchMode || appData.timeDelay == 0)
8550           continue;
8551         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8552           return;
8553         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8554         break;
8555     }
8556 }
8557
8558
8559 int
8560 AutoPlayOneMove()
8561 {
8562     int fromX, fromY, toX, toY;
8563
8564     if (appData.debugMode) {
8565       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8566     }
8567
8568     if (gameMode != PlayFromGameFile)
8569       return FALSE;
8570
8571     if (currentMove >= forwardMostMove) {
8572       gameMode = EditGame;
8573       ModeHighlight();
8574
8575       /* [AS] Clear current move marker at the end of a game */
8576       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8577
8578       return FALSE;
8579     }
8580     
8581     toX = moveList[currentMove][2] - AAA;
8582     toY = moveList[currentMove][3] - ONE;
8583
8584     if (moveList[currentMove][1] == '@') {
8585         if (appData.highlightLastMove) {
8586             SetHighlights(-1, -1, toX, toY);
8587         }
8588     } else {
8589         fromX = moveList[currentMove][0] - AAA;
8590         fromY = moveList[currentMove][1] - ONE;
8591
8592         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8593
8594         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8595
8596         if (appData.highlightLastMove) {
8597             SetHighlights(fromX, fromY, toX, toY);
8598         }
8599     }
8600     DisplayMove(currentMove);
8601     SendMoveToProgram(currentMove++, &first);
8602     DisplayBothClocks();
8603     DrawPosition(FALSE, boards[currentMove]);
8604     // [HGM] PV info: always display, routine tests if empty
8605     DisplayComment(currentMove - 1, commentList[currentMove]);
8606     return TRUE;
8607 }
8608
8609
8610 int
8611 LoadGameOneMove(readAhead)
8612      ChessMove readAhead;
8613 {
8614     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8615     char promoChar = NULLCHAR;
8616     ChessMove moveType;
8617     char move[MSG_SIZ];
8618     char *p, *q;
8619     
8620     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8621         gameMode != AnalyzeMode && gameMode != Training) {
8622         gameFileFP = NULL;
8623         return FALSE;
8624     }
8625     
8626     yyboardindex = forwardMostMove;
8627     if (readAhead != (ChessMove)0) {
8628       moveType = readAhead;
8629     } else {
8630       if (gameFileFP == NULL)
8631           return FALSE;
8632       moveType = (ChessMove) yylex();
8633     }
8634     
8635     done = FALSE;
8636     switch (moveType) {
8637       case Comment:
8638         if (appData.debugMode) 
8639           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8640         p = yy_text;
8641         if (*p == '{' || *p == '[' || *p == '(') {
8642             p[strlen(p) - 1] = NULLCHAR;
8643             p++;
8644         }
8645
8646         /* append the comment but don't display it */
8647         while (*p == '\n') p++;
8648         AppendComment(currentMove, p);
8649         return TRUE;
8650
8651       case WhiteCapturesEnPassant:
8652       case BlackCapturesEnPassant:
8653       case WhitePromotionChancellor:
8654       case BlackPromotionChancellor:
8655       case WhitePromotionArchbishop:
8656       case BlackPromotionArchbishop:
8657       case WhitePromotionCentaur:
8658       case BlackPromotionCentaur:
8659       case WhitePromotionQueen:
8660       case BlackPromotionQueen:
8661       case WhitePromotionRook:
8662       case BlackPromotionRook:
8663       case WhitePromotionBishop:
8664       case BlackPromotionBishop:
8665       case WhitePromotionKnight:
8666       case BlackPromotionKnight:
8667       case WhitePromotionKing:
8668       case BlackPromotionKing:
8669       case NormalMove:
8670       case WhiteKingSideCastle:
8671       case WhiteQueenSideCastle:
8672       case BlackKingSideCastle:
8673       case BlackQueenSideCastle:
8674       case WhiteKingSideCastleWild:
8675       case WhiteQueenSideCastleWild:
8676       case BlackKingSideCastleWild:
8677       case BlackQueenSideCastleWild:
8678       /* PUSH Fabien */
8679       case WhiteHSideCastleFR:
8680       case WhiteASideCastleFR:
8681       case BlackHSideCastleFR:
8682       case BlackASideCastleFR:
8683       /* POP Fabien */
8684         if (appData.debugMode)
8685           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8686         fromX = currentMoveString[0] - AAA;
8687         fromY = currentMoveString[1] - ONE;
8688         toX = currentMoveString[2] - AAA;
8689         toY = currentMoveString[3] - ONE;
8690         promoChar = currentMoveString[4];
8691         break;
8692
8693       case WhiteDrop:
8694       case BlackDrop:
8695         if (appData.debugMode)
8696           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8697         fromX = moveType == WhiteDrop ?
8698           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8699         (int) CharToPiece(ToLower(currentMoveString[0]));
8700         fromY = DROP_RANK;
8701         toX = currentMoveString[2] - AAA;
8702         toY = currentMoveString[3] - ONE;
8703         break;
8704
8705       case WhiteWins:
8706       case BlackWins:
8707       case GameIsDrawn:
8708       case GameUnfinished:
8709         if (appData.debugMode)
8710           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8711         p = strchr(yy_text, '{');
8712         if (p == NULL) p = strchr(yy_text, '(');
8713         if (p == NULL) {
8714             p = yy_text;
8715             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8716         } else {
8717             q = strchr(p, *p == '{' ? '}' : ')');
8718             if (q != NULL) *q = NULLCHAR;
8719             p++;
8720         }
8721         GameEnds(moveType, p, GE_FILE);
8722         done = TRUE;
8723         if (cmailMsgLoaded) {
8724             ClearHighlights();
8725             flipView = WhiteOnMove(currentMove);
8726             if (moveType == GameUnfinished) flipView = !flipView;
8727             if (appData.debugMode)
8728               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8729         }
8730         break;
8731
8732       case (ChessMove) 0:       /* end of file */
8733         if (appData.debugMode)
8734           fprintf(debugFP, "Parser hit end of file\n");
8735         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8736                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8737           case MT_NONE:
8738           case MT_CHECK:
8739             break;
8740           case MT_CHECKMATE:
8741           case MT_STAINMATE:
8742             if (WhiteOnMove(currentMove)) {
8743                 GameEnds(BlackWins, "Black mates", GE_FILE);
8744             } else {
8745                 GameEnds(WhiteWins, "White mates", GE_FILE);
8746             }
8747             break;
8748           case MT_STALEMATE:
8749             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8750             break;
8751         }
8752         done = TRUE;
8753         break;
8754
8755       case MoveNumberOne:
8756         if (lastLoadGameStart == GNUChessGame) {
8757             /* GNUChessGames have numbers, but they aren't move numbers */
8758             if (appData.debugMode)
8759               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8760                       yy_text, (int) moveType);
8761             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8762         }
8763         /* else fall thru */
8764
8765       case XBoardGame:
8766       case GNUChessGame:
8767       case PGNTag:
8768         /* Reached start of next game in file */
8769         if (appData.debugMode)
8770           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8771         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8772                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8773           case MT_NONE:
8774           case MT_CHECK:
8775             break;
8776           case MT_CHECKMATE:
8777           case MT_STAINMATE:
8778             if (WhiteOnMove(currentMove)) {
8779                 GameEnds(BlackWins, "Black mates", GE_FILE);
8780             } else {
8781                 GameEnds(WhiteWins, "White mates", GE_FILE);
8782             }
8783             break;
8784           case MT_STALEMATE:
8785             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8786             break;
8787         }
8788         done = TRUE;
8789         break;
8790
8791       case PositionDiagram:     /* should not happen; ignore */
8792       case ElapsedTime:         /* ignore */
8793       case NAG:                 /* ignore */
8794         if (appData.debugMode)
8795           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8796                   yy_text, (int) moveType);
8797         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8798
8799       case IllegalMove:
8800         if (appData.testLegality) {
8801             if (appData.debugMode)
8802               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8803             sprintf(move, _("Illegal move: %d.%s%s"),
8804                     (forwardMostMove / 2) + 1,
8805                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8806             DisplayError(move, 0);
8807             done = TRUE;
8808         } else {
8809             if (appData.debugMode)
8810               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8811                       yy_text, currentMoveString);
8812             fromX = currentMoveString[0] - AAA;
8813             fromY = currentMoveString[1] - ONE;
8814             toX = currentMoveString[2] - AAA;
8815             toY = currentMoveString[3] - ONE;
8816             promoChar = currentMoveString[4];
8817         }
8818         break;
8819
8820       case AmbiguousMove:
8821         if (appData.debugMode)
8822           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8823         sprintf(move, _("Ambiguous move: %d.%s%s"),
8824                 (forwardMostMove / 2) + 1,
8825                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8826         DisplayError(move, 0);
8827         done = TRUE;
8828         break;
8829
8830       default:
8831       case ImpossibleMove:
8832         if (appData.debugMode)
8833           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8834         sprintf(move, _("Illegal move: %d.%s%s"),
8835                 (forwardMostMove / 2) + 1,
8836                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8837         DisplayError(move, 0);
8838         done = TRUE;
8839         break;
8840     }
8841
8842     if (done) {
8843         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8844             DrawPosition(FALSE, boards[currentMove]);
8845             DisplayBothClocks();
8846             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8847               DisplayComment(currentMove - 1, commentList[currentMove]);
8848         }
8849         (void) StopLoadGameTimer();
8850         gameFileFP = NULL;
8851         cmailOldMove = forwardMostMove;
8852         return FALSE;
8853     } else {
8854         /* currentMoveString is set as a side-effect of yylex */
8855         strcat(currentMoveString, "\n");
8856         strcpy(moveList[forwardMostMove], currentMoveString);
8857         
8858         thinkOutput[0] = NULLCHAR;
8859         MakeMove(fromX, fromY, toX, toY, promoChar);
8860         currentMove = forwardMostMove;
8861         return TRUE;
8862     }
8863 }
8864
8865 /* Load the nth game from the given file */
8866 int
8867 LoadGameFromFile(filename, n, title, useList)
8868      char *filename;
8869      int n;
8870      char *title;
8871      /*Boolean*/ int useList;
8872 {
8873     FILE *f;
8874     char buf[MSG_SIZ];
8875
8876     if (strcmp(filename, "-") == 0) {
8877         f = stdin;
8878         title = "stdin";
8879     } else {
8880         f = fopen(filename, "rb");
8881         if (f == NULL) {
8882           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8883             DisplayError(buf, errno);
8884             return FALSE;
8885         }
8886     }
8887     if (fseek(f, 0, 0) == -1) {
8888         /* f is not seekable; probably a pipe */
8889         useList = FALSE;
8890     }
8891     if (useList && n == 0) {
8892         int error = GameListBuild(f);
8893         if (error) {
8894             DisplayError(_("Cannot build game list"), error);
8895         } else if (!ListEmpty(&gameList) &&
8896                    ((ListGame *) gameList.tailPred)->number > 1) {
8897             GameListPopUp(f, title);
8898             return TRUE;
8899         }
8900         GameListDestroy();
8901         n = 1;
8902     }
8903     if (n == 0) n = 1;
8904     return LoadGame(f, n, title, FALSE);
8905 }
8906
8907
8908 void
8909 MakeRegisteredMove()
8910 {
8911     int fromX, fromY, toX, toY;
8912     char promoChar;
8913     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8914         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8915           case CMAIL_MOVE:
8916           case CMAIL_DRAW:
8917             if (appData.debugMode)
8918               fprintf(debugFP, "Restoring %s for game %d\n",
8919                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8920     
8921             thinkOutput[0] = NULLCHAR;
8922             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8923             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8924             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8925             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8926             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8927             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8928             MakeMove(fromX, fromY, toX, toY, promoChar);
8929             ShowMove(fromX, fromY, toX, toY);
8930               
8931             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8932                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8933               case MT_NONE:
8934               case MT_CHECK:
8935                 break;
8936                 
8937               case MT_CHECKMATE:
8938               case MT_STAINMATE:
8939                 if (WhiteOnMove(currentMove)) {
8940                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8941                 } else {
8942                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8943                 }
8944                 break;
8945                 
8946               case MT_STALEMATE:
8947                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8948                 break;
8949             }
8950
8951             break;
8952             
8953           case CMAIL_RESIGN:
8954             if (WhiteOnMove(currentMove)) {
8955                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8956             } else {
8957                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8958             }
8959             break;
8960             
8961           case CMAIL_ACCEPT:
8962             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8963             break;
8964               
8965           default:
8966             break;
8967         }
8968     }
8969
8970     return;
8971 }
8972
8973 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8974 int
8975 CmailLoadGame(f, gameNumber, title, useList)
8976      FILE *f;
8977      int gameNumber;
8978      char *title;
8979      int useList;
8980 {
8981     int retVal;
8982
8983     if (gameNumber > nCmailGames) {
8984         DisplayError(_("No more games in this message"), 0);
8985         return FALSE;
8986     }
8987     if (f == lastLoadGameFP) {
8988         int offset = gameNumber - lastLoadGameNumber;
8989         if (offset == 0) {
8990             cmailMsg[0] = NULLCHAR;
8991             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8992                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8993                 nCmailMovesRegistered--;
8994             }
8995             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8996             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8997                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8998             }
8999         } else {
9000             if (! RegisterMove()) return FALSE;
9001         }
9002     }
9003
9004     retVal = LoadGame(f, gameNumber, title, useList);
9005
9006     /* Make move registered during previous look at this game, if any */
9007     MakeRegisteredMove();
9008
9009     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9010         commentList[currentMove]
9011           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9012         DisplayComment(currentMove - 1, commentList[currentMove]);
9013     }
9014
9015     return retVal;
9016 }
9017
9018 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9019 int
9020 ReloadGame(offset)
9021      int offset;
9022 {
9023     int gameNumber = lastLoadGameNumber + offset;
9024     if (lastLoadGameFP == NULL) {
9025         DisplayError(_("No game has been loaded yet"), 0);
9026         return FALSE;
9027     }
9028     if (gameNumber <= 0) {
9029         DisplayError(_("Can't back up any further"), 0);
9030         return FALSE;
9031     }
9032     if (cmailMsgLoaded) {
9033         return CmailLoadGame(lastLoadGameFP, gameNumber,
9034                              lastLoadGameTitle, lastLoadGameUseList);
9035     } else {
9036         return LoadGame(lastLoadGameFP, gameNumber,
9037                         lastLoadGameTitle, lastLoadGameUseList);
9038     }
9039 }
9040
9041
9042
9043 /* Load the nth game from open file f */
9044 int
9045 LoadGame(f, gameNumber, title, useList)
9046      FILE *f;
9047      int gameNumber;
9048      char *title;
9049      int useList;
9050 {
9051     ChessMove cm;
9052     char buf[MSG_SIZ];
9053     int gn = gameNumber;
9054     ListGame *lg = NULL;
9055     int numPGNTags = 0;
9056     int err;
9057     GameMode oldGameMode;
9058     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9059
9060     if (appData.debugMode) 
9061         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9062
9063     if (gameMode == Training )
9064         SetTrainingModeOff();
9065
9066     oldGameMode = gameMode;
9067     if (gameMode != BeginningOfGame) {
9068       Reset(FALSE, TRUE);
9069     }
9070
9071     gameFileFP = f;
9072     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9073         fclose(lastLoadGameFP);
9074     }
9075
9076     if (useList) {
9077         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9078         
9079         if (lg) {
9080             fseek(f, lg->offset, 0);
9081             GameListHighlight(gameNumber);
9082             gn = 1;
9083         }
9084         else {
9085             DisplayError(_("Game number out of range"), 0);
9086             return FALSE;
9087         }
9088     } else {
9089         GameListDestroy();
9090         if (fseek(f, 0, 0) == -1) {
9091             if (f == lastLoadGameFP ?
9092                 gameNumber == lastLoadGameNumber + 1 :
9093                 gameNumber == 1) {
9094                 gn = 1;
9095             } else {
9096                 DisplayError(_("Can't seek on game file"), 0);
9097                 return FALSE;
9098             }
9099         }
9100     }
9101     lastLoadGameFP = f;
9102     lastLoadGameNumber = gameNumber;
9103     strcpy(lastLoadGameTitle, title);
9104     lastLoadGameUseList = useList;
9105
9106     yynewfile(f);
9107
9108     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9109       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9110                 lg->gameInfo.black);
9111             DisplayTitle(buf);
9112     } else if (*title != NULLCHAR) {
9113         if (gameNumber > 1) {
9114             sprintf(buf, "%s %d", title, gameNumber);
9115             DisplayTitle(buf);
9116         } else {
9117             DisplayTitle(title);
9118         }
9119     }
9120
9121     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9122         gameMode = PlayFromGameFile;
9123         ModeHighlight();
9124     }
9125
9126     currentMove = forwardMostMove = backwardMostMove = 0;
9127     CopyBoard(boards[0], initialPosition);
9128     StopClocks();
9129
9130     /*
9131      * Skip the first gn-1 games in the file.
9132      * Also skip over anything that precedes an identifiable 
9133      * start of game marker, to avoid being confused by 
9134      * garbage at the start of the file.  Currently 
9135      * recognized start of game markers are the move number "1",
9136      * the pattern "gnuchess .* game", the pattern
9137      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9138      * A game that starts with one of the latter two patterns
9139      * will also have a move number 1, possibly
9140      * following a position diagram.
9141      * 5-4-02: Let's try being more lenient and allowing a game to
9142      * start with an unnumbered move.  Does that break anything?
9143      */
9144     cm = lastLoadGameStart = (ChessMove) 0;
9145     while (gn > 0) {
9146         yyboardindex = forwardMostMove;
9147         cm = (ChessMove) yylex();
9148         switch (cm) {
9149           case (ChessMove) 0:
9150             if (cmailMsgLoaded) {
9151                 nCmailGames = CMAIL_MAX_GAMES - gn;
9152             } else {
9153                 Reset(TRUE, TRUE);
9154                 DisplayError(_("Game not found in file"), 0);
9155             }
9156             return FALSE;
9157
9158           case GNUChessGame:
9159           case XBoardGame:
9160             gn--;
9161             lastLoadGameStart = cm;
9162             break;
9163             
9164           case MoveNumberOne:
9165             switch (lastLoadGameStart) {
9166               case GNUChessGame:
9167               case XBoardGame:
9168               case PGNTag:
9169                 break;
9170               case MoveNumberOne:
9171               case (ChessMove) 0:
9172                 gn--;           /* count this game */
9173                 lastLoadGameStart = cm;
9174                 break;
9175               default:
9176                 /* impossible */
9177                 break;
9178             }
9179             break;
9180
9181           case PGNTag:
9182             switch (lastLoadGameStart) {
9183               case GNUChessGame:
9184               case PGNTag:
9185               case MoveNumberOne:
9186               case (ChessMove) 0:
9187                 gn--;           /* count this game */
9188                 lastLoadGameStart = cm;
9189                 break;
9190               case XBoardGame:
9191                 lastLoadGameStart = cm; /* game counted already */
9192                 break;
9193               default:
9194                 /* impossible */
9195                 break;
9196             }
9197             if (gn > 0) {
9198                 do {
9199                     yyboardindex = forwardMostMove;
9200                     cm = (ChessMove) yylex();
9201                 } while (cm == PGNTag || cm == Comment);
9202             }
9203             break;
9204
9205           case WhiteWins:
9206           case BlackWins:
9207           case GameIsDrawn:
9208             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9209                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9210                     != CMAIL_OLD_RESULT) {
9211                     nCmailResults ++ ;
9212                     cmailResult[  CMAIL_MAX_GAMES
9213                                 - gn - 1] = CMAIL_OLD_RESULT;
9214                 }
9215             }
9216             break;
9217
9218           case NormalMove:
9219             /* Only a NormalMove can be at the start of a game
9220              * without a position diagram. */
9221             if (lastLoadGameStart == (ChessMove) 0) {
9222               gn--;
9223               lastLoadGameStart = MoveNumberOne;
9224             }
9225             break;
9226
9227           default:
9228             break;
9229         }
9230     }
9231     
9232     if (appData.debugMode)
9233       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9234
9235     if (cm == XBoardGame) {
9236         /* Skip any header junk before position diagram and/or move 1 */
9237         for (;;) {
9238             yyboardindex = forwardMostMove;
9239             cm = (ChessMove) yylex();
9240
9241             if (cm == (ChessMove) 0 ||
9242                 cm == GNUChessGame || cm == XBoardGame) {
9243                 /* Empty game; pretend end-of-file and handle later */
9244                 cm = (ChessMove) 0;
9245                 break;
9246             }
9247
9248             if (cm == MoveNumberOne || cm == PositionDiagram ||
9249                 cm == PGNTag || cm == Comment)
9250               break;
9251         }
9252     } else if (cm == GNUChessGame) {
9253         if (gameInfo.event != NULL) {
9254             free(gameInfo.event);
9255         }
9256         gameInfo.event = StrSave(yy_text);
9257     }   
9258
9259     startedFromSetupPosition = FALSE;
9260     while (cm == PGNTag) {
9261         if (appData.debugMode) 
9262           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9263         err = ParsePGNTag(yy_text, &gameInfo);
9264         if (!err) numPGNTags++;
9265
9266         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9267         if(gameInfo.variant != oldVariant) {
9268             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9269             InitPosition(TRUE);
9270             oldVariant = gameInfo.variant;
9271             if (appData.debugMode) 
9272               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9273         }
9274
9275
9276         if (gameInfo.fen != NULL) {
9277           Board initial_position;
9278           startedFromSetupPosition = TRUE;
9279           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9280             Reset(TRUE, TRUE);
9281             DisplayError(_("Bad FEN position in file"), 0);
9282             return FALSE;
9283           }
9284           CopyBoard(boards[0], initial_position);
9285           if (blackPlaysFirst) {
9286             currentMove = forwardMostMove = backwardMostMove = 1;
9287             CopyBoard(boards[1], initial_position);
9288             strcpy(moveList[0], "");
9289             strcpy(parseList[0], "");
9290             timeRemaining[0][1] = whiteTimeRemaining;
9291             timeRemaining[1][1] = blackTimeRemaining;
9292             if (commentList[0] != NULL) {
9293               commentList[1] = commentList[0];
9294               commentList[0] = NULL;
9295             }
9296           } else {
9297             currentMove = forwardMostMove = backwardMostMove = 0;
9298           }
9299           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9300           {   int i;
9301               initialRulePlies = FENrulePlies;
9302               epStatus[forwardMostMove] = FENepStatus;
9303               for( i=0; i< nrCastlingRights; i++ )
9304                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9305           }
9306           yyboardindex = forwardMostMove;
9307           free(gameInfo.fen);
9308           gameInfo.fen = NULL;
9309         }
9310
9311         yyboardindex = forwardMostMove;
9312         cm = (ChessMove) yylex();
9313
9314         /* Handle comments interspersed among the tags */
9315         while (cm == Comment) {
9316             char *p;
9317             if (appData.debugMode) 
9318               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9319             p = yy_text;
9320             if (*p == '{' || *p == '[' || *p == '(') {
9321                 p[strlen(p) - 1] = NULLCHAR;
9322                 p++;
9323             }
9324             while (*p == '\n') p++;
9325             AppendComment(currentMove, p);
9326             yyboardindex = forwardMostMove;
9327             cm = (ChessMove) yylex();
9328         }
9329     }
9330
9331     /* don't rely on existence of Event tag since if game was
9332      * pasted from clipboard the Event tag may not exist
9333      */
9334     if (numPGNTags > 0){
9335         char *tags;
9336         if (gameInfo.variant == VariantNormal) {
9337           gameInfo.variant = StringToVariant(gameInfo.event);
9338         }
9339         if (!matchMode) {
9340           if( appData.autoDisplayTags ) {
9341             tags = PGNTags(&gameInfo);
9342             TagsPopUp(tags, CmailMsg());
9343             free(tags);
9344           }
9345         }
9346     } else {
9347         /* Make something up, but don't display it now */
9348         SetGameInfo();
9349         TagsPopDown();
9350     }
9351
9352     if (cm == PositionDiagram) {
9353         int i, j;
9354         char *p;
9355         Board initial_position;
9356
9357         if (appData.debugMode)
9358           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9359
9360         if (!startedFromSetupPosition) {
9361             p = yy_text;
9362             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9363               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9364                 switch (*p) {
9365                   case '[':
9366                   case '-':
9367                   case ' ':
9368                   case '\t':
9369                   case '\n':
9370                   case '\r':
9371                     break;
9372                   default:
9373                     initial_position[i][j++] = CharToPiece(*p);
9374                     break;
9375                 }
9376             while (*p == ' ' || *p == '\t' ||
9377                    *p == '\n' || *p == '\r') p++;
9378         
9379             if (strncmp(p, "black", strlen("black"))==0)
9380               blackPlaysFirst = TRUE;
9381             else
9382               blackPlaysFirst = FALSE;
9383             startedFromSetupPosition = TRUE;
9384         
9385             CopyBoard(boards[0], initial_position);
9386             if (blackPlaysFirst) {
9387                 currentMove = forwardMostMove = backwardMostMove = 1;
9388                 CopyBoard(boards[1], initial_position);
9389                 strcpy(moveList[0], "");
9390                 strcpy(parseList[0], "");
9391                 timeRemaining[0][1] = whiteTimeRemaining;
9392                 timeRemaining[1][1] = blackTimeRemaining;
9393                 if (commentList[0] != NULL) {
9394                     commentList[1] = commentList[0];
9395                     commentList[0] = NULL;
9396                 }
9397             } else {
9398                 currentMove = forwardMostMove = backwardMostMove = 0;
9399             }
9400         }
9401         yyboardindex = forwardMostMove;
9402         cm = (ChessMove) yylex();
9403     }
9404
9405     if (first.pr == NoProc) {
9406         StartChessProgram(&first);
9407     }
9408     InitChessProgram(&first, FALSE);
9409     SendToProgram("force\n", &first);
9410     if (startedFromSetupPosition) {
9411         SendBoard(&first, forwardMostMove);
9412     if (appData.debugMode) {
9413         fprintf(debugFP, "Load Game\n");
9414     }
9415         DisplayBothClocks();
9416     }      
9417
9418     /* [HGM] server: flag to write setup moves in broadcast file as one */
9419     loadFlag = appData.suppressLoadMoves;
9420
9421     while (cm == Comment) {
9422         char *p;
9423         if (appData.debugMode) 
9424           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9425         p = yy_text;
9426         if (*p == '{' || *p == '[' || *p == '(') {
9427             p[strlen(p) - 1] = NULLCHAR;
9428             p++;
9429         }
9430         while (*p == '\n') p++;
9431         AppendComment(currentMove, p);
9432         yyboardindex = forwardMostMove;
9433         cm = (ChessMove) yylex();
9434     }
9435
9436     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9437         cm == WhiteWins || cm == BlackWins ||
9438         cm == GameIsDrawn || cm == GameUnfinished) {
9439         DisplayMessage("", _("No moves in game"));
9440         if (cmailMsgLoaded) {
9441             if (appData.debugMode)
9442               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9443             ClearHighlights();
9444             flipView = FALSE;
9445         }
9446         DrawPosition(FALSE, boards[currentMove]);
9447         DisplayBothClocks();
9448         gameMode = EditGame;
9449         ModeHighlight();
9450         gameFileFP = NULL;
9451         cmailOldMove = 0;
9452         return TRUE;
9453     }
9454
9455     // [HGM] PV info: routine tests if comment empty
9456     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9457         DisplayComment(currentMove - 1, commentList[currentMove]);
9458     }
9459     if (!matchMode && appData.timeDelay != 0) 
9460       DrawPosition(FALSE, boards[currentMove]);
9461
9462     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9463       programStats.ok_to_send = 1;
9464     }
9465
9466     /* if the first token after the PGN tags is a move
9467      * and not move number 1, retrieve it from the parser 
9468      */
9469     if (cm != MoveNumberOne)
9470         LoadGameOneMove(cm);
9471
9472     /* load the remaining moves from the file */
9473     while (LoadGameOneMove((ChessMove)0)) {
9474       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9475       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9476     }
9477
9478     /* rewind to the start of the game */
9479     currentMove = backwardMostMove;
9480
9481     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9482
9483     if (oldGameMode == AnalyzeFile ||
9484         oldGameMode == AnalyzeMode) {
9485       AnalyzeFileEvent();
9486     }
9487
9488     if (matchMode || appData.timeDelay == 0) {
9489       ToEndEvent();
9490       gameMode = EditGame;
9491       ModeHighlight();
9492     } else if (appData.timeDelay > 0) {
9493       AutoPlayGameLoop();
9494     }
9495
9496     if (appData.debugMode) 
9497         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9498
9499     loadFlag = 0; /* [HGM] true game starts */
9500     return TRUE;
9501 }
9502
9503 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9504 int
9505 ReloadPosition(offset)
9506      int offset;
9507 {
9508     int positionNumber = lastLoadPositionNumber + offset;
9509     if (lastLoadPositionFP == NULL) {
9510         DisplayError(_("No position has been loaded yet"), 0);
9511         return FALSE;
9512     }
9513     if (positionNumber <= 0) {
9514         DisplayError(_("Can't back up any further"), 0);
9515         return FALSE;
9516     }
9517     return LoadPosition(lastLoadPositionFP, positionNumber,
9518                         lastLoadPositionTitle);
9519 }
9520
9521 /* Load the nth position from the given file */
9522 int
9523 LoadPositionFromFile(filename, n, title)
9524      char *filename;
9525      int n;
9526      char *title;
9527 {
9528     FILE *f;
9529     char buf[MSG_SIZ];
9530
9531     if (strcmp(filename, "-") == 0) {
9532         return LoadPosition(stdin, n, "stdin");
9533     } else {
9534         f = fopen(filename, "rb");
9535         if (f == NULL) {
9536             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9537             DisplayError(buf, errno);
9538             return FALSE;
9539         } else {
9540             return LoadPosition(f, n, title);
9541         }
9542     }
9543 }
9544
9545 /* Load the nth position from the given open file, and close it */
9546 int
9547 LoadPosition(f, positionNumber, title)
9548      FILE *f;
9549      int positionNumber;
9550      char *title;
9551 {
9552     char *p, line[MSG_SIZ];
9553     Board initial_position;
9554     int i, j, fenMode, pn;
9555     
9556     if (gameMode == Training )
9557         SetTrainingModeOff();
9558
9559     if (gameMode != BeginningOfGame) {
9560         Reset(FALSE, TRUE);
9561     }
9562     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9563         fclose(lastLoadPositionFP);
9564     }
9565     if (positionNumber == 0) positionNumber = 1;
9566     lastLoadPositionFP = f;
9567     lastLoadPositionNumber = positionNumber;
9568     strcpy(lastLoadPositionTitle, title);
9569     if (first.pr == NoProc) {
9570       StartChessProgram(&first);
9571       InitChessProgram(&first, FALSE);
9572     }    
9573     pn = positionNumber;
9574     if (positionNumber < 0) {
9575         /* Negative position number means to seek to that byte offset */
9576         if (fseek(f, -positionNumber, 0) == -1) {
9577             DisplayError(_("Can't seek on position file"), 0);
9578             return FALSE;
9579         };
9580         pn = 1;
9581     } else {
9582         if (fseek(f, 0, 0) == -1) {
9583             if (f == lastLoadPositionFP ?
9584                 positionNumber == lastLoadPositionNumber + 1 :
9585                 positionNumber == 1) {
9586                 pn = 1;
9587             } else {
9588                 DisplayError(_("Can't seek on position file"), 0);
9589                 return FALSE;
9590             }
9591         }
9592     }
9593     /* See if this file is FEN or old-style xboard */
9594     if (fgets(line, MSG_SIZ, f) == NULL) {
9595         DisplayError(_("Position not found in file"), 0);
9596         return FALSE;
9597     }
9598     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9599     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9600
9601     if (pn >= 2) {
9602         if (fenMode || line[0] == '#') pn--;
9603         while (pn > 0) {
9604             /* skip positions before number pn */
9605             if (fgets(line, MSG_SIZ, f) == NULL) {
9606                 Reset(TRUE, TRUE);
9607                 DisplayError(_("Position not found in file"), 0);
9608                 return FALSE;
9609             }
9610             if (fenMode || line[0] == '#') pn--;
9611         }
9612     }
9613
9614     if (fenMode) {
9615         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9616             DisplayError(_("Bad FEN position in file"), 0);
9617             return FALSE;
9618         }
9619     } else {
9620         (void) fgets(line, MSG_SIZ, f);
9621         (void) fgets(line, MSG_SIZ, f);
9622     
9623         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9624             (void) fgets(line, MSG_SIZ, f);
9625             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9626                 if (*p == ' ')
9627                   continue;
9628                 initial_position[i][j++] = CharToPiece(*p);
9629             }
9630         }
9631     
9632         blackPlaysFirst = FALSE;
9633         if (!feof(f)) {
9634             (void) fgets(line, MSG_SIZ, f);
9635             if (strncmp(line, "black", strlen("black"))==0)
9636               blackPlaysFirst = TRUE;
9637         }
9638     }
9639     startedFromSetupPosition = TRUE;
9640     
9641     SendToProgram("force\n", &first);
9642     CopyBoard(boards[0], initial_position);
9643     if (blackPlaysFirst) {
9644         currentMove = forwardMostMove = backwardMostMove = 1;
9645         strcpy(moveList[0], "");
9646         strcpy(parseList[0], "");
9647         CopyBoard(boards[1], initial_position);
9648         DisplayMessage("", _("Black to play"));
9649     } else {
9650         currentMove = forwardMostMove = backwardMostMove = 0;
9651         DisplayMessage("", _("White to play"));
9652     }
9653           /* [HGM] copy FEN attributes as well */
9654           {   int i;
9655               initialRulePlies = FENrulePlies;
9656               epStatus[forwardMostMove] = FENepStatus;
9657               for( i=0; i< nrCastlingRights; i++ )
9658                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9659           }
9660     SendBoard(&first, forwardMostMove);
9661     if (appData.debugMode) {
9662 int i, j;
9663   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9664   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9665         fprintf(debugFP, "Load Position\n");
9666     }
9667
9668     if (positionNumber > 1) {
9669         sprintf(line, "%s %d", title, positionNumber);
9670         DisplayTitle(line);
9671     } else {
9672         DisplayTitle(title);
9673     }
9674     gameMode = EditGame;
9675     ModeHighlight();
9676     ResetClocks();
9677     timeRemaining[0][1] = whiteTimeRemaining;
9678     timeRemaining[1][1] = blackTimeRemaining;
9679     DrawPosition(FALSE, boards[currentMove]);
9680    
9681     return TRUE;
9682 }
9683
9684
9685 void
9686 CopyPlayerNameIntoFileName(dest, src)
9687      char **dest, *src;
9688 {
9689     while (*src != NULLCHAR && *src != ',') {
9690         if (*src == ' ') {
9691             *(*dest)++ = '_';
9692             src++;
9693         } else {
9694             *(*dest)++ = *src++;
9695         }
9696     }
9697 }
9698
9699 char *DefaultFileName(ext)
9700      char *ext;
9701 {
9702     static char def[MSG_SIZ];
9703     char *p;
9704
9705     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9706         p = def;
9707         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9708         *p++ = '-';
9709         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9710         *p++ = '.';
9711         strcpy(p, ext);
9712     } else {
9713         def[0] = NULLCHAR;
9714     }
9715     return def;
9716 }
9717
9718 /* Save the current game to the given file */
9719 int
9720 SaveGameToFile(filename, append)
9721      char *filename;
9722      int append;
9723 {
9724     FILE *f;
9725     char buf[MSG_SIZ];
9726
9727     if (strcmp(filename, "-") == 0) {
9728         return SaveGame(stdout, 0, NULL);
9729     } else {
9730         f = fopen(filename, append ? "a" : "w");
9731         if (f == NULL) {
9732             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9733             DisplayError(buf, errno);
9734             return FALSE;
9735         } else {
9736             return SaveGame(f, 0, NULL);
9737         }
9738     }
9739 }
9740
9741 char *
9742 SavePart(str)
9743      char *str;
9744 {
9745     static char buf[MSG_SIZ];
9746     char *p;
9747     
9748     p = strchr(str, ' ');
9749     if (p == NULL) return str;
9750     strncpy(buf, str, p - str);
9751     buf[p - str] = NULLCHAR;
9752     return buf;
9753 }
9754
9755 #define PGN_MAX_LINE 75
9756
9757 #define PGN_SIDE_WHITE  0
9758 #define PGN_SIDE_BLACK  1
9759
9760 /* [AS] */
9761 static int FindFirstMoveOutOfBook( int side )
9762 {
9763     int result = -1;
9764
9765     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9766         int index = backwardMostMove;
9767         int has_book_hit = 0;
9768
9769         if( (index % 2) != side ) {
9770             index++;
9771         }
9772
9773         while( index < forwardMostMove ) {
9774             /* Check to see if engine is in book */
9775             int depth = pvInfoList[index].depth;
9776             int score = pvInfoList[index].score;
9777             int in_book = 0;
9778
9779             if( depth <= 2 ) {
9780                 in_book = 1;
9781             }
9782             else if( score == 0 && depth == 63 ) {
9783                 in_book = 1; /* Zappa */
9784             }
9785             else if( score == 2 && depth == 99 ) {
9786                 in_book = 1; /* Abrok */
9787             }
9788
9789             has_book_hit += in_book;
9790
9791             if( ! in_book ) {
9792                 result = index;
9793
9794                 break;
9795             }
9796
9797             index += 2;
9798         }
9799     }
9800
9801     return result;
9802 }
9803
9804 /* [AS] */
9805 void GetOutOfBookInfo( char * buf )
9806 {
9807     int oob[2];
9808     int i;
9809     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9810
9811     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9812     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9813
9814     *buf = '\0';
9815
9816     if( oob[0] >= 0 || oob[1] >= 0 ) {
9817         for( i=0; i<2; i++ ) {
9818             int idx = oob[i];
9819
9820             if( idx >= 0 ) {
9821                 if( i > 0 && oob[0] >= 0 ) {
9822                     strcat( buf, "   " );
9823                 }
9824
9825                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9826                 sprintf( buf+strlen(buf), "%s%.2f", 
9827                     pvInfoList[idx].score >= 0 ? "+" : "",
9828                     pvInfoList[idx].score / 100.0 );
9829             }
9830         }
9831     }
9832 }
9833
9834 /* Save game in PGN style and close the file */
9835 int
9836 SaveGamePGN(f)
9837      FILE *f;
9838 {
9839     int i, offset, linelen, newblock;
9840     time_t tm;
9841 //    char *movetext;
9842     char numtext[32];
9843     int movelen, numlen, blank;
9844     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9845
9846     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9847     
9848     tm = time((time_t *) NULL);
9849     
9850     PrintPGNTags(f, &gameInfo);
9851     
9852     if (backwardMostMove > 0 || startedFromSetupPosition) {
9853         char *fen = PositionToFEN(backwardMostMove, NULL);
9854         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9855         fprintf(f, "\n{--------------\n");
9856         PrintPosition(f, backwardMostMove);
9857         fprintf(f, "--------------}\n");
9858         free(fen);
9859     }
9860     else {
9861         /* [AS] Out of book annotation */
9862         if( appData.saveOutOfBookInfo ) {
9863             char buf[64];
9864
9865             GetOutOfBookInfo( buf );
9866
9867             if( buf[0] != '\0' ) {
9868                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9869             }
9870         }
9871
9872         fprintf(f, "\n");
9873     }
9874
9875     i = backwardMostMove;
9876     linelen = 0;
9877     newblock = TRUE;
9878
9879     while (i < forwardMostMove) {
9880         /* Print comments preceding this move */
9881         if (commentList[i] != NULL) {
9882             if (linelen > 0) fprintf(f, "\n");
9883             fprintf(f, "{\n%s}\n", commentList[i]);
9884             linelen = 0;
9885             newblock = TRUE;
9886         }
9887
9888         /* Format move number */
9889         if ((i % 2) == 0) {
9890             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9891         } else {
9892             if (newblock) {
9893                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9894             } else {
9895                 numtext[0] = NULLCHAR;
9896             }
9897         }
9898         numlen = strlen(numtext);
9899         newblock = FALSE;
9900
9901         /* Print move number */
9902         blank = linelen > 0 && numlen > 0;
9903         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9904             fprintf(f, "\n");
9905             linelen = 0;
9906             blank = 0;
9907         }
9908         if (blank) {
9909             fprintf(f, " ");
9910             linelen++;
9911         }
9912         fprintf(f, "%s", numtext);
9913         linelen += numlen;
9914
9915         /* Get move */
9916         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9917         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9918
9919         /* Print move */
9920         blank = linelen > 0 && movelen > 0;
9921         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9922             fprintf(f, "\n");
9923             linelen = 0;
9924             blank = 0;
9925         }
9926         if (blank) {
9927             fprintf(f, " ");
9928             linelen++;
9929         }
9930         fprintf(f, "%s", move_buffer);
9931         linelen += movelen;
9932
9933         /* [AS] Add PV info if present */
9934         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9935             /* [HGM] add time */
9936             char buf[MSG_SIZ]; int seconds = 0;
9937
9938             if(i >= backwardMostMove) {
9939                 if(WhiteOnMove(i))
9940                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9941                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9942                 else
9943                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9944                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9945             }
9946             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9947
9948             if( seconds <= 0) buf[0] = 0; else
9949             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9950                 seconds = (seconds + 4)/10; // round to full seconds
9951                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9952                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9953             }
9954
9955             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9956                 pvInfoList[i].score >= 0 ? "+" : "",
9957                 pvInfoList[i].score / 100.0,
9958                 pvInfoList[i].depth,
9959                 buf );
9960
9961             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9962
9963             /* Print score/depth */
9964             blank = linelen > 0 && movelen > 0;
9965             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9966                 fprintf(f, "\n");
9967                 linelen = 0;
9968                 blank = 0;
9969             }
9970             if (blank) {
9971                 fprintf(f, " ");
9972                 linelen++;
9973             }
9974             fprintf(f, "%s", move_buffer);
9975             linelen += movelen;
9976         }
9977
9978         i++;
9979     }
9980     
9981     /* Start a new line */
9982     if (linelen > 0) fprintf(f, "\n");
9983
9984     /* Print comments after last move */
9985     if (commentList[i] != NULL) {
9986         fprintf(f, "{\n%s}\n", commentList[i]);
9987     }
9988
9989     /* Print result */
9990     if (gameInfo.resultDetails != NULL &&
9991         gameInfo.resultDetails[0] != NULLCHAR) {
9992         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9993                 PGNResult(gameInfo.result));
9994     } else {
9995         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9996     }
9997
9998     fclose(f);
9999     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10000     return TRUE;
10001 }
10002
10003 /* Save game in old style and close the file */
10004 int
10005 SaveGameOldStyle(f)
10006      FILE *f;
10007 {
10008     int i, offset;
10009     time_t tm;
10010     
10011     tm = time((time_t *) NULL);
10012     
10013     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10014     PrintOpponents(f);
10015     
10016     if (backwardMostMove > 0 || startedFromSetupPosition) {
10017         fprintf(f, "\n[--------------\n");
10018         PrintPosition(f, backwardMostMove);
10019         fprintf(f, "--------------]\n");
10020     } else {
10021         fprintf(f, "\n");
10022     }
10023
10024     i = backwardMostMove;
10025     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10026
10027     while (i < forwardMostMove) {
10028         if (commentList[i] != NULL) {
10029             fprintf(f, "[%s]\n", commentList[i]);
10030         }
10031
10032         if ((i % 2) == 1) {
10033             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10034             i++;
10035         } else {
10036             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10037             i++;
10038             if (commentList[i] != NULL) {
10039                 fprintf(f, "\n");
10040                 continue;
10041             }
10042             if (i >= forwardMostMove) {
10043                 fprintf(f, "\n");
10044                 break;
10045             }
10046             fprintf(f, "%s\n", parseList[i]);
10047             i++;
10048         }
10049     }
10050     
10051     if (commentList[i] != NULL) {
10052         fprintf(f, "[%s]\n", commentList[i]);
10053     }
10054
10055     /* This isn't really the old style, but it's close enough */
10056     if (gameInfo.resultDetails != NULL &&
10057         gameInfo.resultDetails[0] != NULLCHAR) {
10058         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10059                 gameInfo.resultDetails);
10060     } else {
10061         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10062     }
10063
10064     fclose(f);
10065     return TRUE;
10066 }
10067
10068 /* Save the current game to open file f and close the file */
10069 int
10070 SaveGame(f, dummy, dummy2)
10071      FILE *f;
10072      int dummy;
10073      char *dummy2;
10074 {
10075     if (gameMode == EditPosition) EditPositionDone();
10076     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10077     if (appData.oldSaveStyle)
10078       return SaveGameOldStyle(f);
10079     else
10080       return SaveGamePGN(f);
10081 }
10082
10083 /* Save the current position to the given file */
10084 int
10085 SavePositionToFile(filename)
10086      char *filename;
10087 {
10088     FILE *f;
10089     char buf[MSG_SIZ];
10090
10091     if (strcmp(filename, "-") == 0) {
10092         return SavePosition(stdout, 0, NULL);
10093     } else {
10094         f = fopen(filename, "a");
10095         if (f == NULL) {
10096             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10097             DisplayError(buf, errno);
10098             return FALSE;
10099         } else {
10100             SavePosition(f, 0, NULL);
10101             return TRUE;
10102         }
10103     }
10104 }
10105
10106 /* Save the current position to the given open file and close the file */
10107 int
10108 SavePosition(f, dummy, dummy2)
10109      FILE *f;
10110      int dummy;
10111      char *dummy2;
10112 {
10113     time_t tm;
10114     char *fen;
10115     
10116     if (appData.oldSaveStyle) {
10117         tm = time((time_t *) NULL);
10118     
10119         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10120         PrintOpponents(f);
10121         fprintf(f, "[--------------\n");
10122         PrintPosition(f, currentMove);
10123         fprintf(f, "--------------]\n");
10124     } else {
10125         fen = PositionToFEN(currentMove, NULL);
10126         fprintf(f, "%s\n", fen);
10127         free(fen);
10128     }
10129     fclose(f);
10130     return TRUE;
10131 }
10132
10133 void
10134 ReloadCmailMsgEvent(unregister)
10135      int unregister;
10136 {
10137 #if !WIN32
10138     static char *inFilename = NULL;
10139     static char *outFilename;
10140     int i;
10141     struct stat inbuf, outbuf;
10142     int status;
10143     
10144     /* Any registered moves are unregistered if unregister is set, */
10145     /* i.e. invoked by the signal handler */
10146     if (unregister) {
10147         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10148             cmailMoveRegistered[i] = FALSE;
10149             if (cmailCommentList[i] != NULL) {
10150                 free(cmailCommentList[i]);
10151                 cmailCommentList[i] = NULL;
10152             }
10153         }
10154         nCmailMovesRegistered = 0;
10155     }
10156
10157     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10158         cmailResult[i] = CMAIL_NOT_RESULT;
10159     }
10160     nCmailResults = 0;
10161
10162     if (inFilename == NULL) {
10163         /* Because the filenames are static they only get malloced once  */
10164         /* and they never get freed                                      */
10165         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10166         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10167
10168         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10169         sprintf(outFilename, "%s.out", appData.cmailGameName);
10170     }
10171     
10172     status = stat(outFilename, &outbuf);
10173     if (status < 0) {
10174         cmailMailedMove = FALSE;
10175     } else {
10176         status = stat(inFilename, &inbuf);
10177         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10178     }
10179     
10180     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10181        counts the games, notes how each one terminated, etc.
10182        
10183        It would be nice to remove this kludge and instead gather all
10184        the information while building the game list.  (And to keep it
10185        in the game list nodes instead of having a bunch of fixed-size
10186        parallel arrays.)  Note this will require getting each game's
10187        termination from the PGN tags, as the game list builder does
10188        not process the game moves.  --mann
10189        */
10190     cmailMsgLoaded = TRUE;
10191     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10192     
10193     /* Load first game in the file or popup game menu */
10194     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10195
10196 #endif /* !WIN32 */
10197     return;
10198 }
10199
10200 int
10201 RegisterMove()
10202 {
10203     FILE *f;
10204     char string[MSG_SIZ];
10205
10206     if (   cmailMailedMove
10207         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10208         return TRUE;            /* Allow free viewing  */
10209     }
10210
10211     /* Unregister move to ensure that we don't leave RegisterMove        */
10212     /* with the move registered when the conditions for registering no   */
10213     /* longer hold                                                       */
10214     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10215         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10216         nCmailMovesRegistered --;
10217
10218         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10219           {
10220               free(cmailCommentList[lastLoadGameNumber - 1]);
10221               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10222           }
10223     }
10224
10225     if (cmailOldMove == -1) {
10226         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10227         return FALSE;
10228     }
10229
10230     if (currentMove > cmailOldMove + 1) {
10231         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10232         return FALSE;
10233     }
10234
10235     if (currentMove < cmailOldMove) {
10236         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10237         return FALSE;
10238     }
10239
10240     if (forwardMostMove > currentMove) {
10241         /* Silently truncate extra moves */
10242         TruncateGame();
10243     }
10244
10245     if (   (currentMove == cmailOldMove + 1)
10246         || (   (currentMove == cmailOldMove)
10247             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10248                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10249         if (gameInfo.result != GameUnfinished) {
10250             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10251         }
10252
10253         if (commentList[currentMove] != NULL) {
10254             cmailCommentList[lastLoadGameNumber - 1]
10255               = StrSave(commentList[currentMove]);
10256         }
10257         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10258
10259         if (appData.debugMode)
10260           fprintf(debugFP, "Saving %s for game %d\n",
10261                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10262
10263         sprintf(string,
10264                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10265         
10266         f = fopen(string, "w");
10267         if (appData.oldSaveStyle) {
10268             SaveGameOldStyle(f); /* also closes the file */
10269             
10270             sprintf(string, "%s.pos.out", appData.cmailGameName);
10271             f = fopen(string, "w");
10272             SavePosition(f, 0, NULL); /* also closes the file */
10273         } else {
10274             fprintf(f, "{--------------\n");
10275             PrintPosition(f, currentMove);
10276             fprintf(f, "--------------}\n\n");
10277             
10278             SaveGame(f, 0, NULL); /* also closes the file*/
10279         }
10280         
10281         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10282         nCmailMovesRegistered ++;
10283     } else if (nCmailGames == 1) {
10284         DisplayError(_("You have not made a move yet"), 0);
10285         return FALSE;
10286     }
10287
10288     return TRUE;
10289 }
10290
10291 void
10292 MailMoveEvent()
10293 {
10294 #if !WIN32
10295     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10296     FILE *commandOutput;
10297     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10298     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10299     int nBuffers;
10300     int i;
10301     int archived;
10302     char *arcDir;
10303
10304     if (! cmailMsgLoaded) {
10305         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10306         return;
10307     }
10308
10309     if (nCmailGames == nCmailResults) {
10310         DisplayError(_("No unfinished games"), 0);
10311         return;
10312     }
10313
10314 #if CMAIL_PROHIBIT_REMAIL
10315     if (cmailMailedMove) {
10316         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);
10317         DisplayError(msg, 0);
10318         return;
10319     }
10320 #endif
10321
10322     if (! (cmailMailedMove || RegisterMove())) return;
10323     
10324     if (   cmailMailedMove
10325         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10326         sprintf(string, partCommandString,
10327                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10328         commandOutput = popen(string, "r");
10329
10330         if (commandOutput == NULL) {
10331             DisplayError(_("Failed to invoke cmail"), 0);
10332         } else {
10333             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10334                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10335             }
10336             if (nBuffers > 1) {
10337                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10338                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10339                 nBytes = MSG_SIZ - 1;
10340             } else {
10341                 (void) memcpy(msg, buffer, nBytes);
10342             }
10343             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10344
10345             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10346                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10347
10348                 archived = TRUE;
10349                 for (i = 0; i < nCmailGames; i ++) {
10350                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10351                         archived = FALSE;
10352                     }
10353                 }
10354                 if (   archived
10355                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10356                         != NULL)) {
10357                     sprintf(buffer, "%s/%s.%s.archive",
10358                             arcDir,
10359                             appData.cmailGameName,
10360                             gameInfo.date);
10361                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10362                     cmailMsgLoaded = FALSE;
10363                 }
10364             }
10365
10366             DisplayInformation(msg);
10367             pclose(commandOutput);
10368         }
10369     } else {
10370         if ((*cmailMsg) != '\0') {
10371             DisplayInformation(cmailMsg);
10372         }
10373     }
10374
10375     return;
10376 #endif /* !WIN32 */
10377 }
10378
10379 char *
10380 CmailMsg()
10381 {
10382 #if WIN32
10383     return NULL;
10384 #else
10385     int  prependComma = 0;
10386     char number[5];
10387     char string[MSG_SIZ];       /* Space for game-list */
10388     int  i;
10389     
10390     if (!cmailMsgLoaded) return "";
10391
10392     if (cmailMailedMove) {
10393         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10394     } else {
10395         /* Create a list of games left */
10396         sprintf(string, "[");
10397         for (i = 0; i < nCmailGames; i ++) {
10398             if (! (   cmailMoveRegistered[i]
10399                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10400                 if (prependComma) {
10401                     sprintf(number, ",%d", i + 1);
10402                 } else {
10403                     sprintf(number, "%d", i + 1);
10404                     prependComma = 1;
10405                 }
10406                 
10407                 strcat(string, number);
10408             }
10409         }
10410         strcat(string, "]");
10411
10412         if (nCmailMovesRegistered + nCmailResults == 0) {
10413             switch (nCmailGames) {
10414               case 1:
10415                 sprintf(cmailMsg,
10416                         _("Still need to make move for game\n"));
10417                 break;
10418                 
10419               case 2:
10420                 sprintf(cmailMsg,
10421                         _("Still need to make moves for both games\n"));
10422                 break;
10423                 
10424               default:
10425                 sprintf(cmailMsg,
10426                         _("Still need to make moves for all %d games\n"),
10427                         nCmailGames);
10428                 break;
10429             }
10430         } else {
10431             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10432               case 1:
10433                 sprintf(cmailMsg,
10434                         _("Still need to make a move for game %s\n"),
10435                         string);
10436                 break;
10437                 
10438               case 0:
10439                 if (nCmailResults == nCmailGames) {
10440                     sprintf(cmailMsg, _("No unfinished games\n"));
10441                 } else {
10442                     sprintf(cmailMsg, _("Ready to send mail\n"));
10443                 }
10444                 break;
10445                 
10446               default:
10447                 sprintf(cmailMsg,
10448                         _("Still need to make moves for games %s\n"),
10449                         string);
10450             }
10451         }
10452     }
10453     return cmailMsg;
10454 #endif /* WIN32 */
10455 }
10456
10457 void
10458 ResetGameEvent()
10459 {
10460     if (gameMode == Training)
10461       SetTrainingModeOff();
10462
10463     Reset(TRUE, TRUE);
10464     cmailMsgLoaded = FALSE;
10465     if (appData.icsActive) {
10466       SendToICS(ics_prefix);
10467       SendToICS("refresh\n");
10468     }
10469 }
10470
10471 void
10472 ExitEvent(status)
10473      int status;
10474 {
10475     exiting++;
10476     if (exiting > 2) {
10477       /* Give up on clean exit */
10478       exit(status);
10479     }
10480     if (exiting > 1) {
10481       /* Keep trying for clean exit */
10482       return;
10483     }
10484
10485     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10486
10487     if (telnetISR != NULL) {
10488       RemoveInputSource(telnetISR);
10489     }
10490     if (icsPR != NoProc) {
10491       DestroyChildProcess(icsPR, TRUE);
10492     }
10493
10494     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10495     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10496
10497     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10498     /* make sure this other one finishes before killing it!                  */
10499     if(endingGame) { int count = 0;
10500         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10501         while(endingGame && count++ < 10) DoSleep(1);
10502         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10503     }
10504
10505     /* Kill off chess programs */
10506     if (first.pr != NoProc) {
10507         ExitAnalyzeMode();
10508         
10509         DoSleep( appData.delayBeforeQuit );
10510         SendToProgram("quit\n", &first);
10511         DoSleep( appData.delayAfterQuit );
10512         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10513     }
10514     if (second.pr != NoProc) {
10515         DoSleep( appData.delayBeforeQuit );
10516         SendToProgram("quit\n", &second);
10517         DoSleep( appData.delayAfterQuit );
10518         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10519     }
10520     if (first.isr != NULL) {
10521         RemoveInputSource(first.isr);
10522     }
10523     if (second.isr != NULL) {
10524         RemoveInputSource(second.isr);
10525     }
10526
10527     ShutDownFrontEnd();
10528     exit(status);
10529 }
10530
10531 void
10532 PauseEvent()
10533 {
10534     if (appData.debugMode)
10535         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10536     if (pausing) {
10537         pausing = FALSE;
10538         ModeHighlight();
10539         if (gameMode == MachinePlaysWhite ||
10540             gameMode == MachinePlaysBlack) {
10541             StartClocks();
10542         } else {
10543             DisplayBothClocks();
10544         }
10545         if (gameMode == PlayFromGameFile) {
10546             if (appData.timeDelay >= 0) 
10547                 AutoPlayGameLoop();
10548         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10549             Reset(FALSE, TRUE);
10550             SendToICS(ics_prefix);
10551             SendToICS("refresh\n");
10552         } else if (currentMove < forwardMostMove) {
10553             ForwardInner(forwardMostMove);
10554         }
10555         pauseExamInvalid = FALSE;
10556     } else {
10557         switch (gameMode) {
10558           default:
10559             return;
10560           case IcsExamining:
10561             pauseExamForwardMostMove = forwardMostMove;
10562             pauseExamInvalid = FALSE;
10563             /* fall through */
10564           case IcsObserving:
10565           case IcsPlayingWhite:
10566           case IcsPlayingBlack:
10567             pausing = TRUE;
10568             ModeHighlight();
10569             return;
10570           case PlayFromGameFile:
10571             (void) StopLoadGameTimer();
10572             pausing = TRUE;
10573             ModeHighlight();
10574             break;
10575           case BeginningOfGame:
10576             if (appData.icsActive) return;
10577             /* else fall through */
10578           case MachinePlaysWhite:
10579           case MachinePlaysBlack:
10580           case TwoMachinesPlay:
10581             if (forwardMostMove == 0)
10582               return;           /* don't pause if no one has moved */
10583             if ((gameMode == MachinePlaysWhite &&
10584                  !WhiteOnMove(forwardMostMove)) ||
10585                 (gameMode == MachinePlaysBlack &&
10586                  WhiteOnMove(forwardMostMove))) {
10587                 StopClocks();
10588             }
10589             pausing = TRUE;
10590             ModeHighlight();
10591             break;
10592         }
10593     }
10594 }
10595
10596 void
10597 EditCommentEvent()
10598 {
10599     char title[MSG_SIZ];
10600
10601     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10602         strcpy(title, _("Edit comment"));
10603     } else {
10604         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10605                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10606                 parseList[currentMove - 1]);
10607     }
10608
10609     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10610 }
10611
10612
10613 void
10614 EditTagsEvent()
10615 {
10616     char *tags = PGNTags(&gameInfo);
10617     EditTagsPopUp(tags);
10618     free(tags);
10619 }
10620
10621 void
10622 AnalyzeModeEvent()
10623 {
10624     if (appData.noChessProgram || gameMode == AnalyzeMode)
10625       return;
10626
10627     if (gameMode != AnalyzeFile) {
10628         if (!appData.icsEngineAnalyze) {
10629                EditGameEvent();
10630                if (gameMode != EditGame) return;
10631         }
10632         ResurrectChessProgram();
10633         SendToProgram("analyze\n", &first);
10634         first.analyzing = TRUE;
10635         /*first.maybeThinking = TRUE;*/
10636         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10637         EngineOutputPopUp();
10638     }
10639     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10640     pausing = FALSE;
10641     ModeHighlight();
10642     SetGameInfo();
10643
10644     StartAnalysisClock();
10645     GetTimeMark(&lastNodeCountTime);
10646     lastNodeCount = 0;
10647 }
10648
10649 void
10650 AnalyzeFileEvent()
10651 {
10652     if (appData.noChessProgram || gameMode == AnalyzeFile)
10653       return;
10654
10655     if (gameMode != AnalyzeMode) {
10656         EditGameEvent();
10657         if (gameMode != EditGame) return;
10658         ResurrectChessProgram();
10659         SendToProgram("analyze\n", &first);
10660         first.analyzing = TRUE;
10661         /*first.maybeThinking = TRUE;*/
10662         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10663         EngineOutputPopUp();
10664     }
10665     gameMode = AnalyzeFile;
10666     pausing = FALSE;
10667     ModeHighlight();
10668     SetGameInfo();
10669
10670     StartAnalysisClock();
10671     GetTimeMark(&lastNodeCountTime);
10672     lastNodeCount = 0;
10673 }
10674
10675 void
10676 MachineWhiteEvent()
10677 {
10678     char buf[MSG_SIZ];
10679     char *bookHit = NULL;
10680
10681     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10682       return;
10683
10684
10685     if (gameMode == PlayFromGameFile || 
10686         gameMode == TwoMachinesPlay  || 
10687         gameMode == Training         || 
10688         gameMode == AnalyzeMode      || 
10689         gameMode == EndOfGame)
10690         EditGameEvent();
10691
10692     if (gameMode == EditPosition) 
10693         EditPositionDone();
10694
10695     if (!WhiteOnMove(currentMove)) {
10696         DisplayError(_("It is not White's turn"), 0);
10697         return;
10698     }
10699   
10700     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10701       ExitAnalyzeMode();
10702
10703     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10704         gameMode == AnalyzeFile)
10705         TruncateGame();
10706
10707     ResurrectChessProgram();    /* in case it isn't running */
10708     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10709         gameMode = MachinePlaysWhite;
10710         ResetClocks();
10711     } else
10712     gameMode = MachinePlaysWhite;
10713     pausing = FALSE;
10714     ModeHighlight();
10715     SetGameInfo();
10716     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10717     DisplayTitle(buf);
10718     if (first.sendName) {
10719       sprintf(buf, "name %s\n", gameInfo.black);
10720       SendToProgram(buf, &first);
10721     }
10722     if (first.sendTime) {
10723       if (first.useColors) {
10724         SendToProgram("black\n", &first); /*gnu kludge*/
10725       }
10726       SendTimeRemaining(&first, TRUE);
10727     }
10728     if (first.useColors) {
10729       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10730     }
10731     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10732     SetMachineThinkingEnables();
10733     first.maybeThinking = TRUE;
10734     StartClocks();
10735     firstMove = FALSE;
10736
10737     if (appData.autoFlipView && !flipView) {
10738       flipView = !flipView;
10739       DrawPosition(FALSE, NULL);
10740       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10741     }
10742
10743     if(bookHit) { // [HGM] book: simulate book reply
10744         static char bookMove[MSG_SIZ]; // a bit generous?
10745
10746         programStats.nodes = programStats.depth = programStats.time = 
10747         programStats.score = programStats.got_only_move = 0;
10748         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10749
10750         strcpy(bookMove, "move ");
10751         strcat(bookMove, bookHit);
10752         HandleMachineMove(bookMove, &first);
10753     }
10754 }
10755
10756 void
10757 MachineBlackEvent()
10758 {
10759     char buf[MSG_SIZ];
10760    char *bookHit = NULL;
10761
10762     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10763         return;
10764
10765
10766     if (gameMode == PlayFromGameFile || 
10767         gameMode == TwoMachinesPlay  || 
10768         gameMode == Training         || 
10769         gameMode == AnalyzeMode      || 
10770         gameMode == EndOfGame)
10771         EditGameEvent();
10772
10773     if (gameMode == EditPosition) 
10774         EditPositionDone();
10775
10776     if (WhiteOnMove(currentMove)) {
10777         DisplayError(_("It is not Black's turn"), 0);
10778         return;
10779     }
10780     
10781     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10782       ExitAnalyzeMode();
10783
10784     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10785         gameMode == AnalyzeFile)
10786         TruncateGame();
10787
10788     ResurrectChessProgram();    /* in case it isn't running */
10789     gameMode = MachinePlaysBlack;
10790     pausing = FALSE;
10791     ModeHighlight();
10792     SetGameInfo();
10793     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10794     DisplayTitle(buf);
10795     if (first.sendName) {
10796       sprintf(buf, "name %s\n", gameInfo.white);
10797       SendToProgram(buf, &first);
10798     }
10799     if (first.sendTime) {
10800       if (first.useColors) {
10801         SendToProgram("white\n", &first); /*gnu kludge*/
10802       }
10803       SendTimeRemaining(&first, FALSE);
10804     }
10805     if (first.useColors) {
10806       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10807     }
10808     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10809     SetMachineThinkingEnables();
10810     first.maybeThinking = TRUE;
10811     StartClocks();
10812
10813     if (appData.autoFlipView && flipView) {
10814       flipView = !flipView;
10815       DrawPosition(FALSE, NULL);
10816       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10817     }
10818     if(bookHit) { // [HGM] book: simulate book reply
10819         static char bookMove[MSG_SIZ]; // a bit generous?
10820
10821         programStats.nodes = programStats.depth = programStats.time = 
10822         programStats.score = programStats.got_only_move = 0;
10823         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10824
10825         strcpy(bookMove, "move ");
10826         strcat(bookMove, bookHit);
10827         HandleMachineMove(bookMove, &first);
10828     }
10829 }
10830
10831
10832 void
10833 DisplayTwoMachinesTitle()
10834 {
10835     char buf[MSG_SIZ];
10836     if (appData.matchGames > 0) {
10837         if (first.twoMachinesColor[0] == 'w') {
10838             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10839                     gameInfo.white, gameInfo.black,
10840                     first.matchWins, second.matchWins,
10841                     matchGame - 1 - (first.matchWins + second.matchWins));
10842         } else {
10843             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10844                     gameInfo.white, gameInfo.black,
10845                     second.matchWins, first.matchWins,
10846                     matchGame - 1 - (first.matchWins + second.matchWins));
10847         }
10848     } else {
10849         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10850     }
10851     DisplayTitle(buf);
10852 }
10853
10854 void
10855 TwoMachinesEvent P((void))
10856 {
10857     int i;
10858     char buf[MSG_SIZ];
10859     ChessProgramState *onmove;
10860     char *bookHit = NULL;
10861     
10862     if (appData.noChessProgram) return;
10863
10864     switch (gameMode) {
10865       case TwoMachinesPlay:
10866         return;
10867       case MachinePlaysWhite:
10868       case MachinePlaysBlack:
10869         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10870             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10871             return;
10872         }
10873         /* fall through */
10874       case BeginningOfGame:
10875       case PlayFromGameFile:
10876       case EndOfGame:
10877         EditGameEvent();
10878         if (gameMode != EditGame) return;
10879         break;
10880       case EditPosition:
10881         EditPositionDone();
10882         break;
10883       case AnalyzeMode:
10884       case AnalyzeFile:
10885         ExitAnalyzeMode();
10886         break;
10887       case EditGame:
10888       default:
10889         break;
10890     }
10891
10892     forwardMostMove = currentMove;
10893     ResurrectChessProgram();    /* in case first program isn't running */
10894
10895     if (second.pr == NULL) {
10896         StartChessProgram(&second);
10897         if (second.protocolVersion == 1) {
10898           TwoMachinesEventIfReady();
10899         } else {
10900           /* kludge: allow timeout for initial "feature" command */
10901           FreezeUI();
10902           DisplayMessage("", _("Starting second chess program"));
10903           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10904         }
10905         return;
10906     }
10907     DisplayMessage("", "");
10908     InitChessProgram(&second, FALSE);
10909     SendToProgram("force\n", &second);
10910     if (startedFromSetupPosition) {
10911         SendBoard(&second, backwardMostMove);
10912     if (appData.debugMode) {
10913         fprintf(debugFP, "Two Machines\n");
10914     }
10915     }
10916     for (i = backwardMostMove; i < forwardMostMove; i++) {
10917         SendMoveToProgram(i, &second);
10918     }
10919
10920     gameMode = TwoMachinesPlay;
10921     pausing = FALSE;
10922     ModeHighlight();
10923     SetGameInfo();
10924     DisplayTwoMachinesTitle();
10925     firstMove = TRUE;
10926     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10927         onmove = &first;
10928     } else {
10929         onmove = &second;
10930     }
10931
10932     SendToProgram(first.computerString, &first);
10933     if (first.sendName) {
10934       sprintf(buf, "name %s\n", second.tidy);
10935       SendToProgram(buf, &first);
10936     }
10937     SendToProgram(second.computerString, &second);
10938     if (second.sendName) {
10939       sprintf(buf, "name %s\n", first.tidy);
10940       SendToProgram(buf, &second);
10941     }
10942
10943     ResetClocks();
10944     if (!first.sendTime || !second.sendTime) {
10945         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10946         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10947     }
10948     if (onmove->sendTime) {
10949       if (onmove->useColors) {
10950         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10951       }
10952       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10953     }
10954     if (onmove->useColors) {
10955       SendToProgram(onmove->twoMachinesColor, onmove);
10956     }
10957     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10958 //    SendToProgram("go\n", onmove);
10959     onmove->maybeThinking = TRUE;
10960     SetMachineThinkingEnables();
10961
10962     StartClocks();
10963
10964     if(bookHit) { // [HGM] book: simulate book reply
10965         static char bookMove[MSG_SIZ]; // a bit generous?
10966
10967         programStats.nodes = programStats.depth = programStats.time = 
10968         programStats.score = programStats.got_only_move = 0;
10969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10970
10971         strcpy(bookMove, "move ");
10972         strcat(bookMove, bookHit);
10973         savedMessage = bookMove; // args for deferred call
10974         savedState = onmove;
10975         ScheduleDelayedEvent(DeferredBookMove, 1);
10976     }
10977 }
10978
10979 void
10980 TrainingEvent()
10981 {
10982     if (gameMode == Training) {
10983       SetTrainingModeOff();
10984       gameMode = PlayFromGameFile;
10985       DisplayMessage("", _("Training mode off"));
10986     } else {
10987       gameMode = Training;
10988       animateTraining = appData.animate;
10989
10990       /* make sure we are not already at the end of the game */
10991       if (currentMove < forwardMostMove) {
10992         SetTrainingModeOn();
10993         DisplayMessage("", _("Training mode on"));
10994       } else {
10995         gameMode = PlayFromGameFile;
10996         DisplayError(_("Already at end of game"), 0);
10997       }
10998     }
10999     ModeHighlight();
11000 }
11001
11002 void
11003 IcsClientEvent()
11004 {
11005     if (!appData.icsActive) return;
11006     switch (gameMode) {
11007       case IcsPlayingWhite:
11008       case IcsPlayingBlack:
11009       case IcsObserving:
11010       case IcsIdle:
11011       case BeginningOfGame:
11012       case IcsExamining:
11013         return;
11014
11015       case EditGame:
11016         break;
11017
11018       case EditPosition:
11019         EditPositionDone();
11020         break;
11021
11022       case AnalyzeMode:
11023       case AnalyzeFile:
11024         ExitAnalyzeMode();
11025         break;
11026         
11027       default:
11028         EditGameEvent();
11029         break;
11030     }
11031
11032     gameMode = IcsIdle;
11033     ModeHighlight();
11034     return;
11035 }
11036
11037
11038 void
11039 EditGameEvent()
11040 {
11041     int i;
11042
11043     switch (gameMode) {
11044       case Training:
11045         SetTrainingModeOff();
11046         break;
11047       case MachinePlaysWhite:
11048       case MachinePlaysBlack:
11049       case BeginningOfGame:
11050         SendToProgram("force\n", &first);
11051         SetUserThinkingEnables();
11052         break;
11053       case PlayFromGameFile:
11054         (void) StopLoadGameTimer();
11055         if (gameFileFP != NULL) {
11056             gameFileFP = NULL;
11057         }
11058         break;
11059       case EditPosition:
11060         EditPositionDone();
11061         break;
11062       case AnalyzeMode:
11063       case AnalyzeFile:
11064         ExitAnalyzeMode();
11065         SendToProgram("force\n", &first);
11066         break;
11067       case TwoMachinesPlay:
11068         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11069         ResurrectChessProgram();
11070         SetUserThinkingEnables();
11071         break;
11072       case EndOfGame:
11073         ResurrectChessProgram();
11074         break;
11075       case IcsPlayingBlack:
11076       case IcsPlayingWhite:
11077         DisplayError(_("Warning: You are still playing a game"), 0);
11078         break;
11079       case IcsObserving:
11080         DisplayError(_("Warning: You are still observing a game"), 0);
11081         break;
11082       case IcsExamining:
11083         DisplayError(_("Warning: You are still examining a game"), 0);
11084         break;
11085       case IcsIdle:
11086         break;
11087       case EditGame:
11088       default:
11089         return;
11090     }
11091     
11092     pausing = FALSE;
11093     StopClocks();
11094     first.offeredDraw = second.offeredDraw = 0;
11095
11096     if (gameMode == PlayFromGameFile) {
11097         whiteTimeRemaining = timeRemaining[0][currentMove];
11098         blackTimeRemaining = timeRemaining[1][currentMove];
11099         DisplayTitle("");
11100     }
11101
11102     if (gameMode == MachinePlaysWhite ||
11103         gameMode == MachinePlaysBlack ||
11104         gameMode == TwoMachinesPlay ||
11105         gameMode == EndOfGame) {
11106         i = forwardMostMove;
11107         while (i > currentMove) {
11108             SendToProgram("undo\n", &first);
11109             i--;
11110         }
11111         whiteTimeRemaining = timeRemaining[0][currentMove];
11112         blackTimeRemaining = timeRemaining[1][currentMove];
11113         DisplayBothClocks();
11114         if (whiteFlag || blackFlag) {
11115             whiteFlag = blackFlag = 0;
11116         }
11117         DisplayTitle("");
11118     }           
11119     
11120     gameMode = EditGame;
11121     ModeHighlight();
11122     SetGameInfo();
11123 }
11124
11125
11126 void
11127 EditPositionEvent()
11128 {
11129     if (gameMode == EditPosition) {
11130         EditGameEvent();
11131         return;
11132     }
11133     
11134     EditGameEvent();
11135     if (gameMode != EditGame) return;
11136     
11137     gameMode = EditPosition;
11138     ModeHighlight();
11139     SetGameInfo();
11140     if (currentMove > 0)
11141       CopyBoard(boards[0], boards[currentMove]);
11142     
11143     blackPlaysFirst = !WhiteOnMove(currentMove);
11144     ResetClocks();
11145     currentMove = forwardMostMove = backwardMostMove = 0;
11146     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11147     DisplayMove(-1);
11148 }
11149
11150 void
11151 ExitAnalyzeMode()
11152 {
11153     /* [DM] icsEngineAnalyze - possible call from other functions */
11154     if (appData.icsEngineAnalyze) {
11155         appData.icsEngineAnalyze = FALSE;
11156
11157         DisplayMessage("",_("Close ICS engine analyze..."));
11158     }
11159     if (first.analysisSupport && first.analyzing) {
11160       SendToProgram("exit\n", &first);
11161       first.analyzing = FALSE;
11162     }
11163     thinkOutput[0] = NULLCHAR;
11164 }
11165
11166 void
11167 EditPositionDone()
11168 {
11169     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11170
11171     startedFromSetupPosition = TRUE;
11172     InitChessProgram(&first, FALSE);
11173     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11174     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11175         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11176         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11177     } else castlingRights[0][2] = -1;
11178     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11179         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11180         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11181     } else castlingRights[0][5] = -1;
11182     SendToProgram("force\n", &first);
11183     if (blackPlaysFirst) {
11184         strcpy(moveList[0], "");
11185         strcpy(parseList[0], "");
11186         currentMove = forwardMostMove = backwardMostMove = 1;
11187         CopyBoard(boards[1], boards[0]);
11188         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11189         { int i;
11190           epStatus[1] = epStatus[0];
11191           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11192         }
11193     } else {
11194         currentMove = forwardMostMove = backwardMostMove = 0;
11195     }
11196     SendBoard(&first, forwardMostMove);
11197     if (appData.debugMode) {
11198         fprintf(debugFP, "EditPosDone\n");
11199     }
11200     DisplayTitle("");
11201     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11202     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11203     gameMode = EditGame;
11204     ModeHighlight();
11205     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11206     ClearHighlights(); /* [AS] */
11207 }
11208
11209 /* Pause for `ms' milliseconds */
11210 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11211 void
11212 TimeDelay(ms)
11213      long ms;
11214 {
11215     TimeMark m1, m2;
11216
11217     GetTimeMark(&m1);
11218     do {
11219         GetTimeMark(&m2);
11220     } while (SubtractTimeMarks(&m2, &m1) < ms);
11221 }
11222
11223 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11224 void
11225 SendMultiLineToICS(buf)
11226      char *buf;
11227 {
11228     char temp[MSG_SIZ+1], *p;
11229     int len;
11230
11231     len = strlen(buf);
11232     if (len > MSG_SIZ)
11233       len = MSG_SIZ;
11234   
11235     strncpy(temp, buf, len);
11236     temp[len] = 0;
11237
11238     p = temp;
11239     while (*p) {
11240         if (*p == '\n' || *p == '\r')
11241           *p = ' ';
11242         ++p;
11243     }
11244
11245     strcat(temp, "\n");
11246     SendToICS(temp);
11247     SendToPlayer(temp, strlen(temp));
11248 }
11249
11250 void
11251 SetWhiteToPlayEvent()
11252 {
11253     if (gameMode == EditPosition) {
11254         blackPlaysFirst = FALSE;
11255         DisplayBothClocks();    /* works because currentMove is 0 */
11256     } else if (gameMode == IcsExamining) {
11257         SendToICS(ics_prefix);
11258         SendToICS("tomove white\n");
11259     }
11260 }
11261
11262 void
11263 SetBlackToPlayEvent()
11264 {
11265     if (gameMode == EditPosition) {
11266         blackPlaysFirst = TRUE;
11267         currentMove = 1;        /* kludge */
11268         DisplayBothClocks();
11269         currentMove = 0;
11270     } else if (gameMode == IcsExamining) {
11271         SendToICS(ics_prefix);
11272         SendToICS("tomove black\n");
11273     }
11274 }
11275
11276 void
11277 EditPositionMenuEvent(selection, x, y)
11278      ChessSquare selection;
11279      int x, y;
11280 {
11281     char buf[MSG_SIZ];
11282     ChessSquare piece = boards[0][y][x];
11283
11284     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11285
11286     switch (selection) {
11287       case ClearBoard:
11288         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11289             SendToICS(ics_prefix);
11290             SendToICS("bsetup clear\n");
11291         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11292             SendToICS(ics_prefix);
11293             SendToICS("clearboard\n");
11294         } else {
11295             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11296                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11297                 for (y = 0; y < BOARD_HEIGHT; y++) {
11298                     if (gameMode == IcsExamining) {
11299                         if (boards[currentMove][y][x] != EmptySquare) {
11300                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11301                                     AAA + x, ONE + y);
11302                             SendToICS(buf);
11303                         }
11304                     } else {
11305                         boards[0][y][x] = p;
11306                     }
11307                 }
11308             }
11309         }
11310         if (gameMode == EditPosition) {
11311             DrawPosition(FALSE, boards[0]);
11312         }
11313         break;
11314
11315       case WhitePlay:
11316         SetWhiteToPlayEvent();
11317         break;
11318
11319       case BlackPlay:
11320         SetBlackToPlayEvent();
11321         break;
11322
11323       case EmptySquare:
11324         if (gameMode == IcsExamining) {
11325             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11326             SendToICS(buf);
11327         } else {
11328             boards[0][y][x] = EmptySquare;
11329             DrawPosition(FALSE, boards[0]);
11330         }
11331         break;
11332
11333       case PromotePiece:
11334         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11335            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11336             selection = (ChessSquare) (PROMOTED piece);
11337         } else if(piece == EmptySquare) selection = WhiteSilver;
11338         else selection = (ChessSquare)((int)piece - 1);
11339         goto defaultlabel;
11340
11341       case DemotePiece:
11342         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11343            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11344             selection = (ChessSquare) (DEMOTED piece);
11345         } else if(piece == EmptySquare) selection = BlackSilver;
11346         else selection = (ChessSquare)((int)piece + 1);       
11347         goto defaultlabel;
11348
11349       case WhiteQueen:
11350       case BlackQueen:
11351         if(gameInfo.variant == VariantShatranj ||
11352            gameInfo.variant == VariantXiangqi  ||
11353            gameInfo.variant == VariantCourier    )
11354             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11355         goto defaultlabel;
11356
11357       case WhiteKing:
11358       case BlackKing:
11359         if(gameInfo.variant == VariantXiangqi)
11360             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11361         if(gameInfo.variant == VariantKnightmate)
11362             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11363       default:
11364         defaultlabel:
11365         if (gameMode == IcsExamining) {
11366             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11367                     PieceToChar(selection), AAA + x, ONE + y);
11368             SendToICS(buf);
11369         } else {
11370             boards[0][y][x] = selection;
11371             DrawPosition(FALSE, boards[0]);
11372         }
11373         break;
11374     }
11375 }
11376
11377
11378 void
11379 DropMenuEvent(selection, x, y)
11380      ChessSquare selection;
11381      int x, y;
11382 {
11383     ChessMove moveType;
11384
11385     switch (gameMode) {
11386       case IcsPlayingWhite:
11387       case MachinePlaysBlack:
11388         if (!WhiteOnMove(currentMove)) {
11389             DisplayMoveError(_("It is Black's turn"));
11390             return;
11391         }
11392         moveType = WhiteDrop;
11393         break;
11394       case IcsPlayingBlack:
11395       case MachinePlaysWhite:
11396         if (WhiteOnMove(currentMove)) {
11397             DisplayMoveError(_("It is White's turn"));
11398             return;
11399         }
11400         moveType = BlackDrop;
11401         break;
11402       case EditGame:
11403         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11404         break;
11405       default:
11406         return;
11407     }
11408
11409     if (moveType == BlackDrop && selection < BlackPawn) {
11410       selection = (ChessSquare) ((int) selection
11411                                  + (int) BlackPawn - (int) WhitePawn);
11412     }
11413     if (boards[currentMove][y][x] != EmptySquare) {
11414         DisplayMoveError(_("That square is occupied"));
11415         return;
11416     }
11417
11418     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11419 }
11420
11421 void
11422 AcceptEvent()
11423 {
11424     /* Accept a pending offer of any kind from opponent */
11425     
11426     if (appData.icsActive) {
11427         SendToICS(ics_prefix);
11428         SendToICS("accept\n");
11429     } else if (cmailMsgLoaded) {
11430         if (currentMove == cmailOldMove &&
11431             commentList[cmailOldMove] != NULL &&
11432             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11433                    "Black offers a draw" : "White offers a draw")) {
11434             TruncateGame();
11435             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11436             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11437         } else {
11438             DisplayError(_("There is no pending offer on this move"), 0);
11439             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11440         }
11441     } else {
11442         /* Not used for offers from chess program */
11443     }
11444 }
11445
11446 void
11447 DeclineEvent()
11448 {
11449     /* Decline a pending offer of any kind from opponent */
11450     
11451     if (appData.icsActive) {
11452         SendToICS(ics_prefix);
11453         SendToICS("decline\n");
11454     } else if (cmailMsgLoaded) {
11455         if (currentMove == cmailOldMove &&
11456             commentList[cmailOldMove] != NULL &&
11457             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11458                    "Black offers a draw" : "White offers a draw")) {
11459 #ifdef NOTDEF
11460             AppendComment(cmailOldMove, "Draw declined");
11461             DisplayComment(cmailOldMove - 1, "Draw declined");
11462 #endif /*NOTDEF*/
11463         } else {
11464             DisplayError(_("There is no pending offer on this move"), 0);
11465         }
11466     } else {
11467         /* Not used for offers from chess program */
11468     }
11469 }
11470
11471 void
11472 RematchEvent()
11473 {
11474     /* Issue ICS rematch command */
11475     if (appData.icsActive) {
11476         SendToICS(ics_prefix);
11477         SendToICS("rematch\n");
11478     }
11479 }
11480
11481 void
11482 CallFlagEvent()
11483 {
11484     /* Call your opponent's flag (claim a win on time) */
11485     if (appData.icsActive) {
11486         SendToICS(ics_prefix);
11487         SendToICS("flag\n");
11488     } else {
11489         switch (gameMode) {
11490           default:
11491             return;
11492           case MachinePlaysWhite:
11493             if (whiteFlag) {
11494                 if (blackFlag)
11495                   GameEnds(GameIsDrawn, "Both players ran out of time",
11496                            GE_PLAYER);
11497                 else
11498                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11499             } else {
11500                 DisplayError(_("Your opponent is not out of time"), 0);
11501             }
11502             break;
11503           case MachinePlaysBlack:
11504             if (blackFlag) {
11505                 if (whiteFlag)
11506                   GameEnds(GameIsDrawn, "Both players ran out of time",
11507                            GE_PLAYER);
11508                 else
11509                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11510             } else {
11511                 DisplayError(_("Your opponent is not out of time"), 0);
11512             }
11513             break;
11514         }
11515     }
11516 }
11517
11518 void
11519 DrawEvent()
11520 {
11521     /* Offer draw or accept pending draw offer from opponent */
11522     
11523     if (appData.icsActive) {
11524         /* Note: tournament rules require draw offers to be
11525            made after you make your move but before you punch
11526            your clock.  Currently ICS doesn't let you do that;
11527            instead, you immediately punch your clock after making
11528            a move, but you can offer a draw at any time. */
11529         
11530         SendToICS(ics_prefix);
11531         SendToICS("draw\n");
11532     } else if (cmailMsgLoaded) {
11533         if (currentMove == cmailOldMove &&
11534             commentList[cmailOldMove] != NULL &&
11535             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11536                    "Black offers a draw" : "White offers a draw")) {
11537             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11538             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11539         } else if (currentMove == cmailOldMove + 1) {
11540             char *offer = WhiteOnMove(cmailOldMove) ?
11541               "White offers a draw" : "Black offers a draw";
11542             AppendComment(currentMove, offer);
11543             DisplayComment(currentMove - 1, offer);
11544             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11545         } else {
11546             DisplayError(_("You must make your move before offering a draw"), 0);
11547             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11548         }
11549     } else if (first.offeredDraw) {
11550         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11551     } else {
11552         if (first.sendDrawOffers) {
11553             SendToProgram("draw\n", &first);
11554             userOfferedDraw = TRUE;
11555         }
11556     }
11557 }
11558
11559 void
11560 AdjournEvent()
11561 {
11562     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11563     
11564     if (appData.icsActive) {
11565         SendToICS(ics_prefix);
11566         SendToICS("adjourn\n");
11567     } else {
11568         /* Currently GNU Chess doesn't offer or accept Adjourns */
11569     }
11570 }
11571
11572
11573 void
11574 AbortEvent()
11575 {
11576     /* Offer Abort or accept pending Abort offer from opponent */
11577     
11578     if (appData.icsActive) {
11579         SendToICS(ics_prefix);
11580         SendToICS("abort\n");
11581     } else {
11582         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11583     }
11584 }
11585
11586 void
11587 ResignEvent()
11588 {
11589     /* Resign.  You can do this even if it's not your turn. */
11590     
11591     if (appData.icsActive) {
11592         SendToICS(ics_prefix);
11593         SendToICS("resign\n");
11594     } else {
11595         switch (gameMode) {
11596           case MachinePlaysWhite:
11597             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11598             break;
11599           case MachinePlaysBlack:
11600             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11601             break;
11602           case EditGame:
11603             if (cmailMsgLoaded) {
11604                 TruncateGame();
11605                 if (WhiteOnMove(cmailOldMove)) {
11606                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11607                 } else {
11608                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11609                 }
11610                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11611             }
11612             break;
11613           default:
11614             break;
11615         }
11616     }
11617 }
11618
11619
11620 void
11621 StopObservingEvent()
11622 {
11623     /* Stop observing current games */
11624     SendToICS(ics_prefix);
11625     SendToICS("unobserve\n");
11626 }
11627
11628 void
11629 StopExaminingEvent()
11630 {
11631     /* Stop observing current game */
11632     SendToICS(ics_prefix);
11633     SendToICS("unexamine\n");
11634 }
11635
11636 void
11637 ForwardInner(target)
11638      int target;
11639 {
11640     int limit;
11641
11642     if (appData.debugMode)
11643         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11644                 target, currentMove, forwardMostMove);
11645
11646     if (gameMode == EditPosition)
11647       return;
11648
11649     if (gameMode == PlayFromGameFile && !pausing)
11650       PauseEvent();
11651     
11652     if (gameMode == IcsExamining && pausing)
11653       limit = pauseExamForwardMostMove;
11654     else
11655       limit = forwardMostMove;
11656     
11657     if (target > limit) target = limit;
11658
11659     if (target > 0 && moveList[target - 1][0]) {
11660         int fromX, fromY, toX, toY;
11661         toX = moveList[target - 1][2] - AAA;
11662         toY = moveList[target - 1][3] - ONE;
11663         if (moveList[target - 1][1] == '@') {
11664             if (appData.highlightLastMove) {
11665                 SetHighlights(-1, -1, toX, toY);
11666             }
11667         } else {
11668             fromX = moveList[target - 1][0] - AAA;
11669             fromY = moveList[target - 1][1] - ONE;
11670             if (target == currentMove + 1) {
11671                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11672             }
11673             if (appData.highlightLastMove) {
11674                 SetHighlights(fromX, fromY, toX, toY);
11675             }
11676         }
11677     }
11678     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11679         gameMode == Training || gameMode == PlayFromGameFile || 
11680         gameMode == AnalyzeFile) {
11681         while (currentMove < target) {
11682             SendMoveToProgram(currentMove++, &first);
11683         }
11684     } else {
11685         currentMove = target;
11686     }
11687     
11688     if (gameMode == EditGame || gameMode == EndOfGame) {
11689         whiteTimeRemaining = timeRemaining[0][currentMove];
11690         blackTimeRemaining = timeRemaining[1][currentMove];
11691     }
11692     DisplayBothClocks();
11693     DisplayMove(currentMove - 1);
11694     DrawPosition(FALSE, boards[currentMove]);
11695     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11696     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11697         DisplayComment(currentMove - 1, commentList[currentMove]);
11698     }
11699 }
11700
11701
11702 void
11703 ForwardEvent()
11704 {
11705     if (gameMode == IcsExamining && !pausing) {
11706         SendToICS(ics_prefix);
11707         SendToICS("forward\n");
11708     } else {
11709         ForwardInner(currentMove + 1);
11710     }
11711 }
11712
11713 void
11714 ToEndEvent()
11715 {
11716     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11717         /* to optimze, we temporarily turn off analysis mode while we feed
11718          * the remaining moves to the engine. Otherwise we get analysis output
11719          * after each move.
11720          */ 
11721         if (first.analysisSupport) {
11722           SendToProgram("exit\nforce\n", &first);
11723           first.analyzing = FALSE;
11724         }
11725     }
11726         
11727     if (gameMode == IcsExamining && !pausing) {
11728         SendToICS(ics_prefix);
11729         SendToICS("forward 999999\n");
11730     } else {
11731         ForwardInner(forwardMostMove);
11732     }
11733
11734     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11735         /* we have fed all the moves, so reactivate analysis mode */
11736         SendToProgram("analyze\n", &first);
11737         first.analyzing = TRUE;
11738         /*first.maybeThinking = TRUE;*/
11739         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11740     }
11741 }
11742
11743 void
11744 BackwardInner(target)
11745      int target;
11746 {
11747     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11748
11749     if (appData.debugMode)
11750         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11751                 target, currentMove, forwardMostMove);
11752
11753     if (gameMode == EditPosition) return;
11754     if (currentMove <= backwardMostMove) {
11755         ClearHighlights();
11756         DrawPosition(full_redraw, boards[currentMove]);
11757         return;
11758     }
11759     if (gameMode == PlayFromGameFile && !pausing)
11760       PauseEvent();
11761     
11762     if (moveList[target][0]) {
11763         int fromX, fromY, toX, toY;
11764         toX = moveList[target][2] - AAA;
11765         toY = moveList[target][3] - ONE;
11766         if (moveList[target][1] == '@') {
11767             if (appData.highlightLastMove) {
11768                 SetHighlights(-1, -1, toX, toY);
11769             }
11770         } else {
11771             fromX = moveList[target][0] - AAA;
11772             fromY = moveList[target][1] - ONE;
11773             if (target == currentMove - 1) {
11774                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11775             }
11776             if (appData.highlightLastMove) {
11777                 SetHighlights(fromX, fromY, toX, toY);
11778             }
11779         }
11780     }
11781     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11782         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11783         while (currentMove > target) {
11784             SendToProgram("undo\n", &first);
11785             currentMove--;
11786         }
11787     } else {
11788         currentMove = target;
11789     }
11790     
11791     if (gameMode == EditGame || gameMode == EndOfGame) {
11792         whiteTimeRemaining = timeRemaining[0][currentMove];
11793         blackTimeRemaining = timeRemaining[1][currentMove];
11794     }
11795     DisplayBothClocks();
11796     DisplayMove(currentMove - 1);
11797     DrawPosition(full_redraw, boards[currentMove]);
11798     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11799     // [HGM] PV info: routine tests if comment empty
11800     DisplayComment(currentMove - 1, commentList[currentMove]);
11801 }
11802
11803 void
11804 BackwardEvent()
11805 {
11806     if (gameMode == IcsExamining && !pausing) {
11807         SendToICS(ics_prefix);
11808         SendToICS("backward\n");
11809     } else {
11810         BackwardInner(currentMove - 1);
11811     }
11812 }
11813
11814 void
11815 ToStartEvent()
11816 {
11817     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11818         /* to optimze, we temporarily turn off analysis mode while we undo
11819          * all the moves. Otherwise we get analysis output after each undo.
11820          */ 
11821         if (first.analysisSupport) {
11822           SendToProgram("exit\nforce\n", &first);
11823           first.analyzing = FALSE;
11824         }
11825     }
11826
11827     if (gameMode == IcsExamining && !pausing) {
11828         SendToICS(ics_prefix);
11829         SendToICS("backward 999999\n");
11830     } else {
11831         BackwardInner(backwardMostMove);
11832     }
11833
11834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11835         /* we have fed all the moves, so reactivate analysis mode */
11836         SendToProgram("analyze\n", &first);
11837         first.analyzing = TRUE;
11838         /*first.maybeThinking = TRUE;*/
11839         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11840     }
11841 }
11842
11843 void
11844 ToNrEvent(int to)
11845 {
11846   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11847   if (to >= forwardMostMove) to = forwardMostMove;
11848   if (to <= backwardMostMove) to = backwardMostMove;
11849   if (to < currentMove) {
11850     BackwardInner(to);
11851   } else {
11852     ForwardInner(to);
11853   }
11854 }
11855
11856 void
11857 RevertEvent()
11858 {
11859     if (gameMode != IcsExamining) {
11860         DisplayError(_("You are not examining a game"), 0);
11861         return;
11862     }
11863     if (pausing) {
11864         DisplayError(_("You can't revert while pausing"), 0);
11865         return;
11866     }
11867     SendToICS(ics_prefix);
11868     SendToICS("revert\n");
11869 }
11870
11871 void
11872 RetractMoveEvent()
11873 {
11874     switch (gameMode) {
11875       case MachinePlaysWhite:
11876       case MachinePlaysBlack:
11877         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11878             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11879             return;
11880         }
11881         if (forwardMostMove < 2) return;
11882         currentMove = forwardMostMove = forwardMostMove - 2;
11883         whiteTimeRemaining = timeRemaining[0][currentMove];
11884         blackTimeRemaining = timeRemaining[1][currentMove];
11885         DisplayBothClocks();
11886         DisplayMove(currentMove - 1);
11887         ClearHighlights();/*!! could figure this out*/
11888         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11889         SendToProgram("remove\n", &first);
11890         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11891         break;
11892
11893       case BeginningOfGame:
11894       default:
11895         break;
11896
11897       case IcsPlayingWhite:
11898       case IcsPlayingBlack:
11899         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11900             SendToICS(ics_prefix);
11901             SendToICS("takeback 2\n");
11902         } else {
11903             SendToICS(ics_prefix);
11904             SendToICS("takeback 1\n");
11905         }
11906         break;
11907     }
11908 }
11909
11910 void
11911 MoveNowEvent()
11912 {
11913     ChessProgramState *cps;
11914
11915     switch (gameMode) {
11916       case MachinePlaysWhite:
11917         if (!WhiteOnMove(forwardMostMove)) {
11918             DisplayError(_("It is your turn"), 0);
11919             return;
11920         }
11921         cps = &first;
11922         break;
11923       case MachinePlaysBlack:
11924         if (WhiteOnMove(forwardMostMove)) {
11925             DisplayError(_("It is your turn"), 0);
11926             return;
11927         }
11928         cps = &first;
11929         break;
11930       case TwoMachinesPlay:
11931         if (WhiteOnMove(forwardMostMove) ==
11932             (first.twoMachinesColor[0] == 'w')) {
11933             cps = &first;
11934         } else {
11935             cps = &second;
11936         }
11937         break;
11938       case BeginningOfGame:
11939       default:
11940         return;
11941     }
11942     SendToProgram("?\n", cps);
11943 }
11944
11945 void
11946 TruncateGameEvent()
11947 {
11948     EditGameEvent();
11949     if (gameMode != EditGame) return;
11950     TruncateGame();
11951 }
11952
11953 void
11954 TruncateGame()
11955 {
11956     if (forwardMostMove > currentMove) {
11957         if (gameInfo.resultDetails != NULL) {
11958             free(gameInfo.resultDetails);
11959             gameInfo.resultDetails = NULL;
11960             gameInfo.result = GameUnfinished;
11961         }
11962         forwardMostMove = currentMove;
11963         HistorySet(parseList, backwardMostMove, forwardMostMove,
11964                    currentMove-1);
11965     }
11966 }
11967
11968 void
11969 HintEvent()
11970 {
11971     if (appData.noChessProgram) return;
11972     switch (gameMode) {
11973       case MachinePlaysWhite:
11974         if (WhiteOnMove(forwardMostMove)) {
11975             DisplayError(_("Wait until your turn"), 0);
11976             return;
11977         }
11978         break;
11979       case BeginningOfGame:
11980       case MachinePlaysBlack:
11981         if (!WhiteOnMove(forwardMostMove)) {
11982             DisplayError(_("Wait until your turn"), 0);
11983             return;
11984         }
11985         break;
11986       default:
11987         DisplayError(_("No hint available"), 0);
11988         return;
11989     }
11990     SendToProgram("hint\n", &first);
11991     hintRequested = TRUE;
11992 }
11993
11994 void
11995 BookEvent()
11996 {
11997     if (appData.noChessProgram) return;
11998     switch (gameMode) {
11999       case MachinePlaysWhite:
12000         if (WhiteOnMove(forwardMostMove)) {
12001             DisplayError(_("Wait until your turn"), 0);
12002             return;
12003         }
12004         break;
12005       case BeginningOfGame:
12006       case MachinePlaysBlack:
12007         if (!WhiteOnMove(forwardMostMove)) {
12008             DisplayError(_("Wait until your turn"), 0);
12009             return;
12010         }
12011         break;
12012       case EditPosition:
12013         EditPositionDone();
12014         break;
12015       case TwoMachinesPlay:
12016         return;
12017       default:
12018         break;
12019     }
12020     SendToProgram("bk\n", &first);
12021     bookOutput[0] = NULLCHAR;
12022     bookRequested = TRUE;
12023 }
12024
12025 void
12026 AboutGameEvent()
12027 {
12028     char *tags = PGNTags(&gameInfo);
12029     TagsPopUp(tags, CmailMsg());
12030     free(tags);
12031 }
12032
12033 /* end button procedures */
12034
12035 void
12036 PrintPosition(fp, move)
12037      FILE *fp;
12038      int move;
12039 {
12040     int i, j;
12041     
12042     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12043         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12044             char c = PieceToChar(boards[move][i][j]);
12045             fputc(c == 'x' ? '.' : c, fp);
12046             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12047         }
12048     }
12049     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12050       fprintf(fp, "white to play\n");
12051     else
12052       fprintf(fp, "black to play\n");
12053 }
12054
12055 void
12056 PrintOpponents(fp)
12057      FILE *fp;
12058 {
12059     if (gameInfo.white != NULL) {
12060         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12061     } else {
12062         fprintf(fp, "\n");
12063     }
12064 }
12065
12066 /* Find last component of program's own name, using some heuristics */
12067 void
12068 TidyProgramName(prog, host, buf)
12069      char *prog, *host, buf[MSG_SIZ];
12070 {
12071     char *p, *q;
12072     int local = (strcmp(host, "localhost") == 0);
12073     while (!local && (p = strchr(prog, ';')) != NULL) {
12074         p++;
12075         while (*p == ' ') p++;
12076         prog = p;
12077     }
12078     if (*prog == '"' || *prog == '\'') {
12079         q = strchr(prog + 1, *prog);
12080     } else {
12081         q = strchr(prog, ' ');
12082     }
12083     if (q == NULL) q = prog + strlen(prog);
12084     p = q;
12085     while (p >= prog && *p != '/' && *p != '\\') p--;
12086     p++;
12087     if(p == prog && *p == '"') p++;
12088     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12089     memcpy(buf, p, q - p);
12090     buf[q - p] = NULLCHAR;
12091     if (!local) {
12092         strcat(buf, "@");
12093         strcat(buf, host);
12094     }
12095 }
12096
12097 char *
12098 TimeControlTagValue()
12099 {
12100     char buf[MSG_SIZ];
12101     if (!appData.clockMode) {
12102         strcpy(buf, "-");
12103     } else if (movesPerSession > 0) {
12104         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12105     } else if (timeIncrement == 0) {
12106         sprintf(buf, "%ld", timeControl/1000);
12107     } else {
12108         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12109     }
12110     return StrSave(buf);
12111 }
12112
12113 void
12114 SetGameInfo()
12115 {
12116     /* This routine is used only for certain modes */
12117     VariantClass v = gameInfo.variant;
12118     ClearGameInfo(&gameInfo);
12119     gameInfo.variant = v;
12120
12121     switch (gameMode) {
12122       case MachinePlaysWhite:
12123         gameInfo.event = StrSave( appData.pgnEventHeader );
12124         gameInfo.site = StrSave(HostName());
12125         gameInfo.date = PGNDate();
12126         gameInfo.round = StrSave("-");
12127         gameInfo.white = StrSave(first.tidy);
12128         gameInfo.black = StrSave(UserName());
12129         gameInfo.timeControl = TimeControlTagValue();
12130         break;
12131
12132       case MachinePlaysBlack:
12133         gameInfo.event = StrSave( appData.pgnEventHeader );
12134         gameInfo.site = StrSave(HostName());
12135         gameInfo.date = PGNDate();
12136         gameInfo.round = StrSave("-");
12137         gameInfo.white = StrSave(UserName());
12138         gameInfo.black = StrSave(first.tidy);
12139         gameInfo.timeControl = TimeControlTagValue();
12140         break;
12141
12142       case TwoMachinesPlay:
12143         gameInfo.event = StrSave( appData.pgnEventHeader );
12144         gameInfo.site = StrSave(HostName());
12145         gameInfo.date = PGNDate();
12146         if (matchGame > 0) {
12147             char buf[MSG_SIZ];
12148             sprintf(buf, "%d", matchGame);
12149             gameInfo.round = StrSave(buf);
12150         } else {
12151             gameInfo.round = StrSave("-");
12152         }
12153         if (first.twoMachinesColor[0] == 'w') {
12154             gameInfo.white = StrSave(first.tidy);
12155             gameInfo.black = StrSave(second.tidy);
12156         } else {
12157             gameInfo.white = StrSave(second.tidy);
12158             gameInfo.black = StrSave(first.tidy);
12159         }
12160         gameInfo.timeControl = TimeControlTagValue();
12161         break;
12162
12163       case EditGame:
12164         gameInfo.event = StrSave("Edited game");
12165         gameInfo.site = StrSave(HostName());
12166         gameInfo.date = PGNDate();
12167         gameInfo.round = StrSave("-");
12168         gameInfo.white = StrSave("-");
12169         gameInfo.black = StrSave("-");
12170         break;
12171
12172       case EditPosition:
12173         gameInfo.event = StrSave("Edited position");
12174         gameInfo.site = StrSave(HostName());
12175         gameInfo.date = PGNDate();
12176         gameInfo.round = StrSave("-");
12177         gameInfo.white = StrSave("-");
12178         gameInfo.black = StrSave("-");
12179         break;
12180
12181       case IcsPlayingWhite:
12182       case IcsPlayingBlack:
12183       case IcsObserving:
12184       case IcsExamining:
12185         break;
12186
12187       case PlayFromGameFile:
12188         gameInfo.event = StrSave("Game from non-PGN file");
12189         gameInfo.site = StrSave(HostName());
12190         gameInfo.date = PGNDate();
12191         gameInfo.round = StrSave("-");
12192         gameInfo.white = StrSave("?");
12193         gameInfo.black = StrSave("?");
12194         break;
12195
12196       default:
12197         break;
12198     }
12199 }
12200
12201 void
12202 ReplaceComment(index, text)
12203      int index;
12204      char *text;
12205 {
12206     int len;
12207
12208     while (*text == '\n') text++;
12209     len = strlen(text);
12210     while (len > 0 && text[len - 1] == '\n') len--;
12211
12212     if (commentList[index] != NULL)
12213       free(commentList[index]);
12214
12215     if (len == 0) {
12216         commentList[index] = NULL;
12217         return;
12218     }
12219     commentList[index] = (char *) malloc(len + 2);
12220     strncpy(commentList[index], text, len);
12221     commentList[index][len] = '\n';
12222     commentList[index][len + 1] = NULLCHAR;
12223 }
12224
12225 void
12226 CrushCRs(text)
12227      char *text;
12228 {
12229   char *p = text;
12230   char *q = text;
12231   char ch;
12232
12233   do {
12234     ch = *p++;
12235     if (ch == '\r') continue;
12236     *q++ = ch;
12237   } while (ch != '\0');
12238 }
12239
12240 void
12241 AppendComment(index, text)
12242      int index;
12243      char *text;
12244 {
12245     int oldlen, len;
12246     char *old;
12247
12248     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12249
12250     CrushCRs(text);
12251     while (*text == '\n') text++;
12252     len = strlen(text);
12253     while (len > 0 && text[len - 1] == '\n') len--;
12254
12255     if (len == 0) return;
12256
12257     if (commentList[index] != NULL) {
12258         old = commentList[index];
12259         oldlen = strlen(old);
12260         commentList[index] = (char *) malloc(oldlen + len + 2);
12261         strcpy(commentList[index], old);
12262         free(old);
12263         strncpy(&commentList[index][oldlen], text, len);
12264         commentList[index][oldlen + len] = '\n';
12265         commentList[index][oldlen + len + 1] = NULLCHAR;
12266     } else {
12267         commentList[index] = (char *) malloc(len + 2);
12268         strncpy(commentList[index], text, len);
12269         commentList[index][len] = '\n';
12270         commentList[index][len + 1] = NULLCHAR;
12271     }
12272 }
12273
12274 static char * FindStr( char * text, char * sub_text )
12275 {
12276     char * result = strstr( text, sub_text );
12277
12278     if( result != NULL ) {
12279         result += strlen( sub_text );
12280     }
12281
12282     return result;
12283 }
12284
12285 /* [AS] Try to extract PV info from PGN comment */
12286 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12287 char *GetInfoFromComment( int index, char * text )
12288 {
12289     char * sep = text;
12290
12291     if( text != NULL && index > 0 ) {
12292         int score = 0;
12293         int depth = 0;
12294         int time = -1, sec = 0, deci;
12295         char * s_eval = FindStr( text, "[%eval " );
12296         char * s_emt = FindStr( text, "[%emt " );
12297
12298         if( s_eval != NULL || s_emt != NULL ) {
12299             /* New style */
12300             char delim;
12301
12302             if( s_eval != NULL ) {
12303                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12304                     return text;
12305                 }
12306
12307                 if( delim != ']' ) {
12308                     return text;
12309                 }
12310             }
12311
12312             if( s_emt != NULL ) {
12313             }
12314         }
12315         else {
12316             /* We expect something like: [+|-]nnn.nn/dd */
12317             int score_lo = 0;
12318
12319             sep = strchr( text, '/' );
12320             if( sep == NULL || sep < (text+4) ) {
12321                 return text;
12322             }
12323
12324             time = -1; sec = -1; deci = -1;
12325             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12326                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12327                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12328                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12329                 return text;
12330             }
12331
12332             if( score_lo < 0 || score_lo >= 100 ) {
12333                 return text;
12334             }
12335
12336             if(sec >= 0) time = 600*time + 10*sec; else
12337             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12338
12339             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12340
12341             /* [HGM] PV time: now locate end of PV info */
12342             while( *++sep >= '0' && *sep <= '9'); // strip depth
12343             if(time >= 0)
12344             while( *++sep >= '0' && *sep <= '9'); // strip time
12345             if(sec >= 0)
12346             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12347             if(deci >= 0)
12348             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12349             while(*sep == ' ') sep++;
12350         }
12351
12352         if( depth <= 0 ) {
12353             return text;
12354         }
12355
12356         if( time < 0 ) {
12357             time = -1;
12358         }
12359
12360         pvInfoList[index-1].depth = depth;
12361         pvInfoList[index-1].score = score;
12362         pvInfoList[index-1].time  = 10*time; // centi-sec
12363     }
12364     return sep;
12365 }
12366
12367 void
12368 SendToProgram(message, cps)
12369      char *message;
12370      ChessProgramState *cps;
12371 {
12372     int count, outCount, error;
12373     char buf[MSG_SIZ];
12374
12375     if (cps->pr == NULL) return;
12376     Attention(cps);
12377     
12378     if (appData.debugMode) {
12379         TimeMark now;
12380         GetTimeMark(&now);
12381         fprintf(debugFP, "%ld >%-6s: %s", 
12382                 SubtractTimeMarks(&now, &programStartTime),
12383                 cps->which, message);
12384     }
12385     
12386     count = strlen(message);
12387     outCount = OutputToProcess(cps->pr, message, count, &error);
12388     if (outCount < count && !exiting 
12389                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12390         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12391         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12392             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12393                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12394                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12395             } else {
12396                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12397             }
12398             gameInfo.resultDetails = buf;
12399         }
12400         DisplayFatalError(buf, error, 1);
12401     }
12402 }
12403
12404 void
12405 ReceiveFromProgram(isr, closure, message, count, error)
12406      InputSourceRef isr;
12407      VOIDSTAR closure;
12408      char *message;
12409      int count;
12410      int error;
12411 {
12412     char *end_str;
12413     char buf[MSG_SIZ];
12414     ChessProgramState *cps = (ChessProgramState *)closure;
12415
12416     if (isr != cps->isr) return; /* Killed intentionally */
12417     if (count <= 0) {
12418         if (count == 0) {
12419             sprintf(buf,
12420                     _("Error: %s chess program (%s) exited unexpectedly"),
12421                     cps->which, cps->program);
12422         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12423                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12424                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12425                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12426                 } else {
12427                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12428                 }
12429                 gameInfo.resultDetails = buf;
12430             }
12431             RemoveInputSource(cps->isr);
12432             DisplayFatalError(buf, 0, 1);
12433         } else {
12434             sprintf(buf,
12435                     _("Error reading from %s chess program (%s)"),
12436                     cps->which, cps->program);
12437             RemoveInputSource(cps->isr);
12438
12439             /* [AS] Program is misbehaving badly... kill it */
12440             if( count == -2 ) {
12441                 DestroyChildProcess( cps->pr, 9 );
12442                 cps->pr = NoProc;
12443             }
12444
12445             DisplayFatalError(buf, error, 1);
12446         }
12447         return;
12448     }
12449     
12450     if ((end_str = strchr(message, '\r')) != NULL)
12451       *end_str = NULLCHAR;
12452     if ((end_str = strchr(message, '\n')) != NULL)
12453       *end_str = NULLCHAR;
12454     
12455     if (appData.debugMode) {
12456         TimeMark now; int print = 1;
12457         char *quote = ""; char c; int i;
12458
12459         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12460                 char start = message[0];
12461                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12462                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12463                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12464                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12465                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12466                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12467                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12468                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12469                         { quote = "# "; print = (appData.engineComments == 2); }
12470                 message[0] = start; // restore original message
12471         }
12472         if(print) {
12473                 GetTimeMark(&now);
12474                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12475                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12476                         quote,
12477                         message);
12478         }
12479     }
12480
12481     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12482     if (appData.icsEngineAnalyze) {
12483         if (strstr(message, "whisper") != NULL ||
12484              strstr(message, "kibitz") != NULL || 
12485             strstr(message, "tellics") != NULL) return;
12486     }
12487
12488     HandleMachineMove(message, cps);
12489 }
12490
12491
12492 void
12493 SendTimeControl(cps, mps, tc, inc, sd, st)
12494      ChessProgramState *cps;
12495      int mps, inc, sd, st;
12496      long tc;
12497 {
12498     char buf[MSG_SIZ];
12499     int seconds;
12500
12501     if( timeControl_2 > 0 ) {
12502         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12503             tc = timeControl_2;
12504         }
12505     }
12506     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12507     inc /= cps->timeOdds;
12508     st  /= cps->timeOdds;
12509
12510     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12511
12512     if (st > 0) {
12513       /* Set exact time per move, normally using st command */
12514       if (cps->stKludge) {
12515         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12516         seconds = st % 60;
12517         if (seconds == 0) {
12518           sprintf(buf, "level 1 %d\n", st/60);
12519         } else {
12520           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12521         }
12522       } else {
12523         sprintf(buf, "st %d\n", st);
12524       }
12525     } else {
12526       /* Set conventional or incremental time control, using level command */
12527       if (seconds == 0) {
12528         /* Note old gnuchess bug -- minutes:seconds used to not work.
12529            Fixed in later versions, but still avoid :seconds
12530            when seconds is 0. */
12531         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12532       } else {
12533         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12534                 seconds, inc/1000);
12535       }
12536     }
12537     SendToProgram(buf, cps);
12538
12539     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12540     /* Orthogonally, limit search to given depth */
12541     if (sd > 0) {
12542       if (cps->sdKludge) {
12543         sprintf(buf, "depth\n%d\n", sd);
12544       } else {
12545         sprintf(buf, "sd %d\n", sd);
12546       }
12547       SendToProgram(buf, cps);
12548     }
12549
12550     if(cps->nps > 0) { /* [HGM] nps */
12551         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12552         else {
12553                 sprintf(buf, "nps %d\n", cps->nps);
12554               SendToProgram(buf, cps);
12555         }
12556     }
12557 }
12558
12559 ChessProgramState *WhitePlayer()
12560 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12561 {
12562     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12563        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12564         return &second;
12565     return &first;
12566 }
12567
12568 void
12569 SendTimeRemaining(cps, machineWhite)
12570      ChessProgramState *cps;
12571      int /*boolean*/ machineWhite;
12572 {
12573     char message[MSG_SIZ];
12574     long time, otime;
12575
12576     /* Note: this routine must be called when the clocks are stopped
12577        or when they have *just* been set or switched; otherwise
12578        it will be off by the time since the current tick started.
12579     */
12580     if (machineWhite) {
12581         time = whiteTimeRemaining / 10;
12582         otime = blackTimeRemaining / 10;
12583     } else {
12584         time = blackTimeRemaining / 10;
12585         otime = whiteTimeRemaining / 10;
12586     }
12587     /* [HGM] translate opponent's time by time-odds factor */
12588     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12589     if (appData.debugMode) {
12590         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12591     }
12592
12593     if (time <= 0) time = 1;
12594     if (otime <= 0) otime = 1;
12595     
12596     sprintf(message, "time %ld\n", time);
12597     SendToProgram(message, cps);
12598
12599     sprintf(message, "otim %ld\n", otime);
12600     SendToProgram(message, cps);
12601 }
12602
12603 int
12604 BoolFeature(p, name, loc, cps)
12605      char **p;
12606      char *name;
12607      int *loc;
12608      ChessProgramState *cps;
12609 {
12610   char buf[MSG_SIZ];
12611   int len = strlen(name);
12612   int val;
12613   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12614     (*p) += len + 1;
12615     sscanf(*p, "%d", &val);
12616     *loc = (val != 0);
12617     while (**p && **p != ' ') (*p)++;
12618     sprintf(buf, "accepted %s\n", name);
12619     SendToProgram(buf, cps);
12620     return TRUE;
12621   }
12622   return FALSE;
12623 }
12624
12625 int
12626 IntFeature(p, name, loc, cps)
12627      char **p;
12628      char *name;
12629      int *loc;
12630      ChessProgramState *cps;
12631 {
12632   char buf[MSG_SIZ];
12633   int len = strlen(name);
12634   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12635     (*p) += len + 1;
12636     sscanf(*p, "%d", loc);
12637     while (**p && **p != ' ') (*p)++;
12638     sprintf(buf, "accepted %s\n", name);
12639     SendToProgram(buf, cps);
12640     return TRUE;
12641   }
12642   return FALSE;
12643 }
12644
12645 int
12646 StringFeature(p, name, loc, cps)
12647      char **p;
12648      char *name;
12649      char loc[];
12650      ChessProgramState *cps;
12651 {
12652   char buf[MSG_SIZ];
12653   int len = strlen(name);
12654   if (strncmp((*p), name, len) == 0
12655       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12656     (*p) += len + 2;
12657     sscanf(*p, "%[^\"]", loc);
12658     while (**p && **p != '\"') (*p)++;
12659     if (**p == '\"') (*p)++;
12660     sprintf(buf, "accepted %s\n", name);
12661     SendToProgram(buf, cps);
12662     return TRUE;
12663   }
12664   return FALSE;
12665 }
12666
12667 int 
12668 ParseOption(Option *opt, ChessProgramState *cps)
12669 // [HGM] options: process the string that defines an engine option, and determine
12670 // name, type, default value, and allowed value range
12671 {
12672         char *p, *q, buf[MSG_SIZ];
12673         int n, min = (-1)<<31, max = 1<<31, def;
12674
12675         if(p = strstr(opt->name, " -spin ")) {
12676             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12677             if(max < min) max = min; // enforce consistency
12678             if(def < min) def = min;
12679             if(def > max) def = max;
12680             opt->value = def;
12681             opt->min = min;
12682             opt->max = max;
12683             opt->type = Spin;
12684         } else if((p = strstr(opt->name, " -slider "))) {
12685             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12686             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12687             if(max < min) max = min; // enforce consistency
12688             if(def < min) def = min;
12689             if(def > max) def = max;
12690             opt->value = def;
12691             opt->min = min;
12692             opt->max = max;
12693             opt->type = Spin; // Slider;
12694         } else if((p = strstr(opt->name, " -string "))) {
12695             opt->textValue = p+9;
12696             opt->type = TextBox;
12697         } else if((p = strstr(opt->name, " -file "))) {
12698             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12699             opt->textValue = p+7;
12700             opt->type = TextBox; // FileName;
12701         } else if((p = strstr(opt->name, " -path "))) {
12702             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12703             opt->textValue = p+7;
12704             opt->type = TextBox; // PathName;
12705         } else if(p = strstr(opt->name, " -check ")) {
12706             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12707             opt->value = (def != 0);
12708             opt->type = CheckBox;
12709         } else if(p = strstr(opt->name, " -combo ")) {
12710             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12711             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12712             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12713             opt->value = n = 0;
12714             while(q = StrStr(q, " /// ")) {
12715                 n++; *q = 0;    // count choices, and null-terminate each of them
12716                 q += 5;
12717                 if(*q == '*') { // remember default, which is marked with * prefix
12718                     q++;
12719                     opt->value = n;
12720                 }
12721                 cps->comboList[cps->comboCnt++] = q;
12722             }
12723             cps->comboList[cps->comboCnt++] = NULL;
12724             opt->max = n + 1;
12725             opt->type = ComboBox;
12726         } else if(p = strstr(opt->name, " -button")) {
12727             opt->type = Button;
12728         } else if(p = strstr(opt->name, " -save")) {
12729             opt->type = SaveButton;
12730         } else return FALSE;
12731         *p = 0; // terminate option name
12732         // now look if the command-line options define a setting for this engine option.
12733         if(cps->optionSettings && cps->optionSettings[0])
12734             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12735         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12736                 sprintf(buf, "option %s", p);
12737                 if(p = strstr(buf, ",")) *p = 0;
12738                 strcat(buf, "\n");
12739                 SendToProgram(buf, cps);
12740         }
12741         return TRUE;
12742 }
12743
12744 void
12745 FeatureDone(cps, val)
12746      ChessProgramState* cps;
12747      int val;
12748 {
12749   DelayedEventCallback cb = GetDelayedEvent();
12750   if ((cb == InitBackEnd3 && cps == &first) ||
12751       (cb == TwoMachinesEventIfReady && cps == &second)) {
12752     CancelDelayedEvent();
12753     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12754   }
12755   cps->initDone = val;
12756 }
12757
12758 /* Parse feature command from engine */
12759 void
12760 ParseFeatures(args, cps)
12761      char* args;
12762      ChessProgramState *cps;  
12763 {
12764   char *p = args;
12765   char *q;
12766   int val;
12767   char buf[MSG_SIZ];
12768
12769   for (;;) {
12770     while (*p == ' ') p++;
12771     if (*p == NULLCHAR) return;
12772
12773     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12774     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12775     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12776     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12777     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12778     if (BoolFeature(&p, "reuse", &val, cps)) {
12779       /* Engine can disable reuse, but can't enable it if user said no */
12780       if (!val) cps->reuse = FALSE;
12781       continue;
12782     }
12783     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12784     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12785       if (gameMode == TwoMachinesPlay) {
12786         DisplayTwoMachinesTitle();
12787       } else {
12788         DisplayTitle("");
12789       }
12790       continue;
12791     }
12792     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12793     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12794     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12795     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12796     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12797     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12798     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12799     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12800     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12801     if (IntFeature(&p, "done", &val, cps)) {
12802       FeatureDone(cps, val);
12803       continue;
12804     }
12805     /* Added by Tord: */
12806     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12807     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12808     /* End of additions by Tord */
12809
12810     /* [HGM] added features: */
12811     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12812     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12813     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12814     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12815     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12816     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12817     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12818         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12819             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12820             SendToProgram(buf, cps);
12821             continue;
12822         }
12823         if(cps->nrOptions >= MAX_OPTIONS) {
12824             cps->nrOptions--;
12825             sprintf(buf, "%s engine has too many options\n", cps->which);
12826             DisplayError(buf, 0);
12827         }
12828         continue;
12829     }
12830     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12831     /* End of additions by HGM */
12832
12833     /* unknown feature: complain and skip */
12834     q = p;
12835     while (*q && *q != '=') q++;
12836     sprintf(buf, "rejected %.*s\n", q-p, p);
12837     SendToProgram(buf, cps);
12838     p = q;
12839     if (*p == '=') {
12840       p++;
12841       if (*p == '\"') {
12842         p++;
12843         while (*p && *p != '\"') p++;
12844         if (*p == '\"') p++;
12845       } else {
12846         while (*p && *p != ' ') p++;
12847       }
12848     }
12849   }
12850
12851 }
12852
12853 void
12854 PeriodicUpdatesEvent(newState)
12855      int newState;
12856 {
12857     if (newState == appData.periodicUpdates)
12858       return;
12859
12860     appData.periodicUpdates=newState;
12861
12862     /* Display type changes, so update it now */
12863 //    DisplayAnalysis();
12864
12865     /* Get the ball rolling again... */
12866     if (newState) {
12867         AnalysisPeriodicEvent(1);
12868         StartAnalysisClock();
12869     }
12870 }
12871
12872 void
12873 PonderNextMoveEvent(newState)
12874      int newState;
12875 {
12876     if (newState == appData.ponderNextMove) return;
12877     if (gameMode == EditPosition) EditPositionDone();
12878     if (newState) {
12879         SendToProgram("hard\n", &first);
12880         if (gameMode == TwoMachinesPlay) {
12881             SendToProgram("hard\n", &second);
12882         }
12883     } else {
12884         SendToProgram("easy\n", &first);
12885         thinkOutput[0] = NULLCHAR;
12886         if (gameMode == TwoMachinesPlay) {
12887             SendToProgram("easy\n", &second);
12888         }
12889     }
12890     appData.ponderNextMove = newState;
12891 }
12892
12893 void
12894 NewSettingEvent(option, command, value)
12895      char *command;
12896      int option, value;
12897 {
12898     char buf[MSG_SIZ];
12899
12900     if (gameMode == EditPosition) EditPositionDone();
12901     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12902     SendToProgram(buf, &first);
12903     if (gameMode == TwoMachinesPlay) {
12904         SendToProgram(buf, &second);
12905     }
12906 }
12907
12908 void
12909 ShowThinkingEvent()
12910 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12911 {
12912     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12913     int newState = appData.showThinking
12914         // [HGM] thinking: other features now need thinking output as well
12915         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12916     
12917     if (oldState == newState) return;
12918     oldState = newState;
12919     if (gameMode == EditPosition) EditPositionDone();
12920     if (oldState) {
12921         SendToProgram("post\n", &first);
12922         if (gameMode == TwoMachinesPlay) {
12923             SendToProgram("post\n", &second);
12924         }
12925     } else {
12926         SendToProgram("nopost\n", &first);
12927         thinkOutput[0] = NULLCHAR;
12928         if (gameMode == TwoMachinesPlay) {
12929             SendToProgram("nopost\n", &second);
12930         }
12931     }
12932 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12933 }
12934
12935 void
12936 AskQuestionEvent(title, question, replyPrefix, which)
12937      char *title; char *question; char *replyPrefix; char *which;
12938 {
12939   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12940   if (pr == NoProc) return;
12941   AskQuestion(title, question, replyPrefix, pr);
12942 }
12943
12944 void
12945 DisplayMove(moveNumber)
12946      int moveNumber;
12947 {
12948     char message[MSG_SIZ];
12949     char res[MSG_SIZ];
12950     char cpThinkOutput[MSG_SIZ];
12951
12952     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12953     
12954     if (moveNumber == forwardMostMove - 1 || 
12955         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12956
12957         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12958
12959         if (strchr(cpThinkOutput, '\n')) {
12960             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12961         }
12962     } else {
12963         *cpThinkOutput = NULLCHAR;
12964     }
12965
12966     /* [AS] Hide thinking from human user */
12967     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12968         *cpThinkOutput = NULLCHAR;
12969         if( thinkOutput[0] != NULLCHAR ) {
12970             int i;
12971
12972             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12973                 cpThinkOutput[i] = '.';
12974             }
12975             cpThinkOutput[i] = NULLCHAR;
12976             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12977         }
12978     }
12979
12980     if (moveNumber == forwardMostMove - 1 &&
12981         gameInfo.resultDetails != NULL) {
12982         if (gameInfo.resultDetails[0] == NULLCHAR) {
12983             sprintf(res, " %s", PGNResult(gameInfo.result));
12984         } else {
12985             sprintf(res, " {%s} %s",
12986                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12987         }
12988     } else {
12989         res[0] = NULLCHAR;
12990     }
12991
12992     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12993         DisplayMessage(res, cpThinkOutput);
12994     } else {
12995         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12996                 WhiteOnMove(moveNumber) ? " " : ".. ",
12997                 parseList[moveNumber], res);
12998         DisplayMessage(message, cpThinkOutput);
12999     }
13000 }
13001
13002 void
13003 DisplayComment(moveNumber, text)
13004      int moveNumber;
13005      char *text;
13006 {
13007     char title[MSG_SIZ];
13008     char buf[8000]; // comment can be long!
13009     int score, depth;
13010
13011     if( appData.autoDisplayComment ) {
13012         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13013             strcpy(title, "Comment");
13014         } else {
13015             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13016                     WhiteOnMove(moveNumber) ? " " : ".. ",
13017                     parseList[moveNumber]);
13018         }
13019         // [HGM] PV info: display PV info together with (or as) comment
13020         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13021             if(text == NULL) text = "";                                           
13022             score = pvInfoList[moveNumber].score;
13023             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13024                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13025             text = buf;
13026         }
13027     } else title[0] = 0;
13028
13029     if (text != NULL)
13030         CommentPopUp(title, text);
13031 }
13032
13033 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13034  * might be busy thinking or pondering.  It can be omitted if your
13035  * gnuchess is configured to stop thinking immediately on any user
13036  * input.  However, that gnuchess feature depends on the FIONREAD
13037  * ioctl, which does not work properly on some flavors of Unix.
13038  */
13039 void
13040 Attention(cps)
13041      ChessProgramState *cps;
13042 {
13043 #if ATTENTION
13044     if (!cps->useSigint) return;
13045     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13046     switch (gameMode) {
13047       case MachinePlaysWhite:
13048       case MachinePlaysBlack:
13049       case TwoMachinesPlay:
13050       case IcsPlayingWhite:
13051       case IcsPlayingBlack:
13052       case AnalyzeMode:
13053       case AnalyzeFile:
13054         /* Skip if we know it isn't thinking */
13055         if (!cps->maybeThinking) return;
13056         if (appData.debugMode)
13057           fprintf(debugFP, "Interrupting %s\n", cps->which);
13058         InterruptChildProcess(cps->pr);
13059         cps->maybeThinking = FALSE;
13060         break;
13061       default:
13062         break;
13063     }
13064 #endif /*ATTENTION*/
13065 }
13066
13067 int
13068 CheckFlags()
13069 {
13070     if (whiteTimeRemaining <= 0) {
13071         if (!whiteFlag) {
13072             whiteFlag = TRUE;
13073             if (appData.icsActive) {
13074                 if (appData.autoCallFlag &&
13075                     gameMode == IcsPlayingBlack && !blackFlag) {
13076                   SendToICS(ics_prefix);
13077                   SendToICS("flag\n");
13078                 }
13079             } else {
13080                 if (blackFlag) {
13081                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13082                 } else {
13083                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13084                     if (appData.autoCallFlag) {
13085                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13086                         return TRUE;
13087                     }
13088                 }
13089             }
13090         }
13091     }
13092     if (blackTimeRemaining <= 0) {
13093         if (!blackFlag) {
13094             blackFlag = TRUE;
13095             if (appData.icsActive) {
13096                 if (appData.autoCallFlag &&
13097                     gameMode == IcsPlayingWhite && !whiteFlag) {
13098                   SendToICS(ics_prefix);
13099                   SendToICS("flag\n");
13100                 }
13101             } else {
13102                 if (whiteFlag) {
13103                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13104                 } else {
13105                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13106                     if (appData.autoCallFlag) {
13107                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13108                         return TRUE;
13109                     }
13110                 }
13111             }
13112         }
13113     }
13114     return FALSE;
13115 }
13116
13117 void
13118 CheckTimeControl()
13119 {
13120     if (!appData.clockMode || appData.icsActive ||
13121         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13122
13123     /*
13124      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13125      */
13126     if ( !WhiteOnMove(forwardMostMove) )
13127         /* White made time control */
13128         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13129         /* [HGM] time odds: correct new time quota for time odds! */
13130                                             / WhitePlayer()->timeOdds;
13131       else
13132         /* Black made time control */
13133         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13134                                             / WhitePlayer()->other->timeOdds;
13135 }
13136
13137 void
13138 DisplayBothClocks()
13139 {
13140     int wom = gameMode == EditPosition ?
13141       !blackPlaysFirst : WhiteOnMove(currentMove);
13142     DisplayWhiteClock(whiteTimeRemaining, wom);
13143     DisplayBlackClock(blackTimeRemaining, !wom);
13144 }
13145
13146
13147 /* Timekeeping seems to be a portability nightmare.  I think everyone
13148    has ftime(), but I'm really not sure, so I'm including some ifdefs
13149    to use other calls if you don't.  Clocks will be less accurate if
13150    you have neither ftime nor gettimeofday.
13151 */
13152
13153 /* VS 2008 requires the #include outside of the function */
13154 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13155 #include <sys/timeb.h>
13156 #endif
13157
13158 /* Get the current time as a TimeMark */
13159 void
13160 GetTimeMark(tm)
13161      TimeMark *tm;
13162 {
13163 #if HAVE_GETTIMEOFDAY
13164
13165     struct timeval timeVal;
13166     struct timezone timeZone;
13167
13168     gettimeofday(&timeVal, &timeZone);
13169     tm->sec = (long) timeVal.tv_sec; 
13170     tm->ms = (int) (timeVal.tv_usec / 1000L);
13171
13172 #else /*!HAVE_GETTIMEOFDAY*/
13173 #if HAVE_FTIME
13174
13175 // include <sys/timeb.h> / moved to just above start of function
13176     struct timeb timeB;
13177
13178     ftime(&timeB);
13179     tm->sec = (long) timeB.time;
13180     tm->ms = (int) timeB.millitm;
13181
13182 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13183     tm->sec = (long) time(NULL);
13184     tm->ms = 0;
13185 #endif
13186 #endif
13187 }
13188
13189 /* Return the difference in milliseconds between two
13190    time marks.  We assume the difference will fit in a long!
13191 */
13192 long
13193 SubtractTimeMarks(tm2, tm1)
13194      TimeMark *tm2, *tm1;
13195 {
13196     return 1000L*(tm2->sec - tm1->sec) +
13197            (long) (tm2->ms - tm1->ms);
13198 }
13199
13200
13201 /*
13202  * Code to manage the game clocks.
13203  *
13204  * In tournament play, black starts the clock and then white makes a move.
13205  * We give the human user a slight advantage if he is playing white---the
13206  * clocks don't run until he makes his first move, so it takes zero time.
13207  * Also, we don't account for network lag, so we could get out of sync
13208  * with GNU Chess's clock -- but then, referees are always right.  
13209  */
13210
13211 static TimeMark tickStartTM;
13212 static long intendedTickLength;
13213
13214 long
13215 NextTickLength(timeRemaining)
13216      long timeRemaining;
13217 {
13218     long nominalTickLength, nextTickLength;
13219
13220     if (timeRemaining > 0L && timeRemaining <= 10000L)
13221       nominalTickLength = 100L;
13222     else
13223       nominalTickLength = 1000L;
13224     nextTickLength = timeRemaining % nominalTickLength;
13225     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13226
13227     return nextTickLength;
13228 }
13229
13230 /* Adjust clock one minute up or down */
13231 void
13232 AdjustClock(Boolean which, int dir)
13233 {
13234     if(which) blackTimeRemaining += 60000*dir;
13235     else      whiteTimeRemaining += 60000*dir;
13236     DisplayBothClocks();
13237 }
13238
13239 /* Stop clocks and reset to a fresh time control */
13240 void
13241 ResetClocks() 
13242 {
13243     (void) StopClockTimer();
13244     if (appData.icsActive) {
13245         whiteTimeRemaining = blackTimeRemaining = 0;
13246     } else { /* [HGM] correct new time quote for time odds */
13247         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13248         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13249     }
13250     if (whiteFlag || blackFlag) {
13251         DisplayTitle("");
13252         whiteFlag = blackFlag = FALSE;
13253     }
13254     DisplayBothClocks();
13255 }
13256
13257 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13258
13259 /* Decrement running clock by amount of time that has passed */
13260 void
13261 DecrementClocks()
13262 {
13263     long timeRemaining;
13264     long lastTickLength, fudge;
13265     TimeMark now;
13266
13267     if (!appData.clockMode) return;
13268     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13269         
13270     GetTimeMark(&now);
13271
13272     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13273
13274     /* Fudge if we woke up a little too soon */
13275     fudge = intendedTickLength - lastTickLength;
13276     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13277
13278     if (WhiteOnMove(forwardMostMove)) {
13279         if(whiteNPS >= 0) lastTickLength = 0;
13280         timeRemaining = whiteTimeRemaining -= lastTickLength;
13281         DisplayWhiteClock(whiteTimeRemaining - fudge,
13282                           WhiteOnMove(currentMove));
13283     } else {
13284         if(blackNPS >= 0) lastTickLength = 0;
13285         timeRemaining = blackTimeRemaining -= lastTickLength;
13286         DisplayBlackClock(blackTimeRemaining - fudge,
13287                           !WhiteOnMove(currentMove));
13288     }
13289
13290     if (CheckFlags()) return;
13291         
13292     tickStartTM = now;
13293     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13294     StartClockTimer(intendedTickLength);
13295
13296     /* if the time remaining has fallen below the alarm threshold, sound the
13297      * alarm. if the alarm has sounded and (due to a takeback or time control
13298      * with increment) the time remaining has increased to a level above the
13299      * threshold, reset the alarm so it can sound again. 
13300      */
13301     
13302     if (appData.icsActive && appData.icsAlarm) {
13303
13304         /* make sure we are dealing with the user's clock */
13305         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13306                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13307            )) return;
13308
13309         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13310             alarmSounded = FALSE;
13311         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13312             PlayAlarmSound();
13313             alarmSounded = TRUE;
13314         }
13315     }
13316 }
13317
13318
13319 /* A player has just moved, so stop the previously running
13320    clock and (if in clock mode) start the other one.
13321    We redisplay both clocks in case we're in ICS mode, because
13322    ICS gives us an update to both clocks after every move.
13323    Note that this routine is called *after* forwardMostMove
13324    is updated, so the last fractional tick must be subtracted
13325    from the color that is *not* on move now.
13326 */
13327 void
13328 SwitchClocks()
13329 {
13330     long lastTickLength;
13331     TimeMark now;
13332     int flagged = FALSE;
13333
13334     GetTimeMark(&now);
13335
13336     if (StopClockTimer() && appData.clockMode) {
13337         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13338         if (WhiteOnMove(forwardMostMove)) {
13339             if(blackNPS >= 0) lastTickLength = 0;
13340             blackTimeRemaining -= lastTickLength;
13341            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13342 //         if(pvInfoList[forwardMostMove-1].time == -1)
13343                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13344                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13345         } else {
13346            if(whiteNPS >= 0) lastTickLength = 0;
13347            whiteTimeRemaining -= lastTickLength;
13348            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13349 //         if(pvInfoList[forwardMostMove-1].time == -1)
13350                  pvInfoList[forwardMostMove-1].time = 
13351                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13352         }
13353         flagged = CheckFlags();
13354     }
13355     CheckTimeControl();
13356
13357     if (flagged || !appData.clockMode) return;
13358
13359     switch (gameMode) {
13360       case MachinePlaysBlack:
13361       case MachinePlaysWhite:
13362       case BeginningOfGame:
13363         if (pausing) return;
13364         break;
13365
13366       case EditGame:
13367       case PlayFromGameFile:
13368       case IcsExamining:
13369         return;
13370
13371       default:
13372         break;
13373     }
13374
13375     tickStartTM = now;
13376     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13377       whiteTimeRemaining : blackTimeRemaining);
13378     StartClockTimer(intendedTickLength);
13379 }
13380         
13381
13382 /* Stop both clocks */
13383 void
13384 StopClocks()
13385 {       
13386     long lastTickLength;
13387     TimeMark now;
13388
13389     if (!StopClockTimer()) return;
13390     if (!appData.clockMode) return;
13391
13392     GetTimeMark(&now);
13393
13394     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13395     if (WhiteOnMove(forwardMostMove)) {
13396         if(whiteNPS >= 0) lastTickLength = 0;
13397         whiteTimeRemaining -= lastTickLength;
13398         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13399     } else {
13400         if(blackNPS >= 0) lastTickLength = 0;
13401         blackTimeRemaining -= lastTickLength;
13402         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13403     }
13404     CheckFlags();
13405 }
13406         
13407 /* Start clock of player on move.  Time may have been reset, so
13408    if clock is already running, stop and restart it. */
13409 void
13410 StartClocks()
13411 {
13412     (void) StopClockTimer(); /* in case it was running already */
13413     DisplayBothClocks();
13414     if (CheckFlags()) return;
13415
13416     if (!appData.clockMode) return;
13417     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13418
13419     GetTimeMark(&tickStartTM);
13420     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13421       whiteTimeRemaining : blackTimeRemaining);
13422
13423    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13424     whiteNPS = blackNPS = -1; 
13425     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13426        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13427         whiteNPS = first.nps;
13428     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13429        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13430         blackNPS = first.nps;
13431     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13432         whiteNPS = second.nps;
13433     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13434         blackNPS = second.nps;
13435     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13436
13437     StartClockTimer(intendedTickLength);
13438 }
13439
13440 char *
13441 TimeString(ms)
13442      long ms;
13443 {
13444     long second, minute, hour, day;
13445     char *sign = "";
13446     static char buf[32];
13447     
13448     if (ms > 0 && ms <= 9900) {
13449       /* convert milliseconds to tenths, rounding up */
13450       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13451
13452       sprintf(buf, " %03.1f ", tenths/10.0);
13453       return buf;
13454     }
13455
13456     /* convert milliseconds to seconds, rounding up */
13457     /* use floating point to avoid strangeness of integer division
13458        with negative dividends on many machines */
13459     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13460
13461     if (second < 0) {
13462         sign = "-";
13463         second = -second;
13464     }
13465     
13466     day = second / (60 * 60 * 24);
13467     second = second % (60 * 60 * 24);
13468     hour = second / (60 * 60);
13469     second = second % (60 * 60);
13470     minute = second / 60;
13471     second = second % 60;
13472     
13473     if (day > 0)
13474       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13475               sign, day, hour, minute, second);
13476     else if (hour > 0)
13477       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13478     else
13479       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13480     
13481     return buf;
13482 }
13483
13484
13485 /*
13486  * This is necessary because some C libraries aren't ANSI C compliant yet.
13487  */
13488 char *
13489 StrStr(string, match)
13490      char *string, *match;
13491 {
13492     int i, length;
13493     
13494     length = strlen(match);
13495     
13496     for (i = strlen(string) - length; i >= 0; i--, string++)
13497       if (!strncmp(match, string, length))
13498         return string;
13499     
13500     return NULL;
13501 }
13502
13503 char *
13504 StrCaseStr(string, match)
13505      char *string, *match;
13506 {
13507     int i, j, length;
13508     
13509     length = strlen(match);
13510     
13511     for (i = strlen(string) - length; i >= 0; i--, string++) {
13512         for (j = 0; j < length; j++) {
13513             if (ToLower(match[j]) != ToLower(string[j]))
13514               break;
13515         }
13516         if (j == length) return string;
13517     }
13518
13519     return NULL;
13520 }
13521
13522 #ifndef _amigados
13523 int
13524 StrCaseCmp(s1, s2)
13525      char *s1, *s2;
13526 {
13527     char c1, c2;
13528     
13529     for (;;) {
13530         c1 = ToLower(*s1++);
13531         c2 = ToLower(*s2++);
13532         if (c1 > c2) return 1;
13533         if (c1 < c2) return -1;
13534         if (c1 == NULLCHAR) return 0;
13535     }
13536 }
13537
13538
13539 int
13540 ToLower(c)
13541      int c;
13542 {
13543     return isupper(c) ? tolower(c) : c;
13544 }
13545
13546
13547 int
13548 ToUpper(c)
13549      int c;
13550 {
13551     return islower(c) ? toupper(c) : c;
13552 }
13553 #endif /* !_amigados    */
13554
13555 char *
13556 StrSave(s)
13557      char *s;
13558 {
13559     char *ret;
13560
13561     if ((ret = (char *) malloc(strlen(s) + 1))) {
13562         strcpy(ret, s);
13563     }
13564     return ret;
13565 }
13566
13567 char *
13568 StrSavePtr(s, savePtr)
13569      char *s, **savePtr;
13570 {
13571     if (*savePtr) {
13572         free(*savePtr);
13573     }
13574     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13575         strcpy(*savePtr, s);
13576     }
13577     return(*savePtr);
13578 }
13579
13580 char *
13581 PGNDate()
13582 {
13583     time_t clock;
13584     struct tm *tm;
13585     char buf[MSG_SIZ];
13586
13587     clock = time((time_t *)NULL);
13588     tm = localtime(&clock);
13589     sprintf(buf, "%04d.%02d.%02d",
13590             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13591     return StrSave(buf);
13592 }
13593
13594
13595 char *
13596 PositionToFEN(move, overrideCastling)
13597      int move;
13598      char *overrideCastling;
13599 {
13600     int i, j, fromX, fromY, toX, toY;
13601     int whiteToPlay;
13602     char buf[128];
13603     char *p, *q;
13604     int emptycount;
13605     ChessSquare piece;
13606
13607     whiteToPlay = (gameMode == EditPosition) ?
13608       !blackPlaysFirst : (move % 2 == 0);
13609     p = buf;
13610
13611     /* Piece placement data */
13612     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13613         emptycount = 0;
13614         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13615             if (boards[move][i][j] == EmptySquare) {
13616                 emptycount++;
13617             } else { ChessSquare piece = boards[move][i][j];
13618                 if (emptycount > 0) {
13619                     if(emptycount<10) /* [HGM] can be >= 10 */
13620                         *p++ = '0' + emptycount;
13621                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13622                     emptycount = 0;
13623                 }
13624                 if(PieceToChar(piece) == '+') {
13625                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13626                     *p++ = '+';
13627                     piece = (ChessSquare)(DEMOTED piece);
13628                 } 
13629                 *p++ = PieceToChar(piece);
13630                 if(p[-1] == '~') {
13631                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13632                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13633                     *p++ = '~';
13634                 }
13635             }
13636         }
13637         if (emptycount > 0) {
13638             if(emptycount<10) /* [HGM] can be >= 10 */
13639                 *p++ = '0' + emptycount;
13640             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13641             emptycount = 0;
13642         }
13643         *p++ = '/';
13644     }
13645     *(p - 1) = ' ';
13646
13647     /* [HGM] print Crazyhouse or Shogi holdings */
13648     if( gameInfo.holdingsWidth ) {
13649         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13650         q = p;
13651         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13652             piece = boards[move][i][BOARD_WIDTH-1];
13653             if( piece != EmptySquare )
13654               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13655                   *p++ = PieceToChar(piece);
13656         }
13657         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13658             piece = boards[move][BOARD_HEIGHT-i-1][0];
13659             if( piece != EmptySquare )
13660               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13661                   *p++ = PieceToChar(piece);
13662         }
13663
13664         if( q == p ) *p++ = '-';
13665         *p++ = ']';
13666         *p++ = ' ';
13667     }
13668
13669     /* Active color */
13670     *p++ = whiteToPlay ? 'w' : 'b';
13671     *p++ = ' ';
13672
13673   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13674     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13675   } else {
13676   if(nrCastlingRights) {
13677      q = p;
13678      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13679        /* [HGM] write directly from rights */
13680            if(castlingRights[move][2] >= 0 &&
13681               castlingRights[move][0] >= 0   )
13682                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13683            if(castlingRights[move][2] >= 0 &&
13684               castlingRights[move][1] >= 0   )
13685                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13686            if(castlingRights[move][5] >= 0 &&
13687               castlingRights[move][3] >= 0   )
13688                 *p++ = castlingRights[move][3] + AAA;
13689            if(castlingRights[move][5] >= 0 &&
13690               castlingRights[move][4] >= 0   )
13691                 *p++ = castlingRights[move][4] + AAA;
13692      } else {
13693
13694         /* [HGM] write true castling rights */
13695         if( nrCastlingRights == 6 ) {
13696             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13697                castlingRights[move][2] >= 0  ) *p++ = 'K';
13698             if(castlingRights[move][1] == BOARD_LEFT &&
13699                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13700             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13701                castlingRights[move][5] >= 0  ) *p++ = 'k';
13702             if(castlingRights[move][4] == BOARD_LEFT &&
13703                castlingRights[move][5] >= 0  ) *p++ = 'q';
13704         }
13705      }
13706      if (q == p) *p++ = '-'; /* No castling rights */
13707      *p++ = ' ';
13708   }
13709
13710   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13711      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13712     /* En passant target square */
13713     if (move > backwardMostMove) {
13714         fromX = moveList[move - 1][0] - AAA;
13715         fromY = moveList[move - 1][1] - ONE;
13716         toX = moveList[move - 1][2] - AAA;
13717         toY = moveList[move - 1][3] - ONE;
13718         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13719             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13720             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13721             fromX == toX) {
13722             /* 2-square pawn move just happened */
13723             *p++ = toX + AAA;
13724             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13725         } else {
13726             *p++ = '-';
13727         }
13728     } else if(move == backwardMostMove) {
13729         // [HGM] perhaps we should always do it like this, and forget the above?
13730         if(epStatus[move] >= 0) {
13731             *p++ = epStatus[move] + AAA;
13732             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13733         } else {
13734             *p++ = '-';
13735         }
13736     } else {
13737         *p++ = '-';
13738     }
13739     *p++ = ' ';
13740   }
13741   }
13742
13743     /* [HGM] find reversible plies */
13744     {   int i = 0, j=move;
13745
13746         if (appData.debugMode) { int k;
13747             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13748             for(k=backwardMostMove; k<=forwardMostMove; k++)
13749                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13750
13751         }
13752
13753         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13754         if( j == backwardMostMove ) i += initialRulePlies;
13755         sprintf(p, "%d ", i);
13756         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13757     }
13758     /* Fullmove number */
13759     sprintf(p, "%d", (move / 2) + 1);
13760     
13761     return StrSave(buf);
13762 }
13763
13764 Boolean
13765 ParseFEN(board, blackPlaysFirst, fen)
13766     Board board;
13767      int *blackPlaysFirst;
13768      char *fen;
13769 {
13770     int i, j;
13771     char *p;
13772     int emptycount;
13773     ChessSquare piece;
13774
13775     p = fen;
13776
13777     /* [HGM] by default clear Crazyhouse holdings, if present */
13778     if(gameInfo.holdingsWidth) {
13779        for(i=0; i<BOARD_HEIGHT; i++) {
13780            board[i][0]             = EmptySquare; /* black holdings */
13781            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13782            board[i][1]             = (ChessSquare) 0; /* black counts */
13783            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13784        }
13785     }
13786
13787     /* Piece placement data */
13788     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13789         j = 0;
13790         for (;;) {
13791             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13792                 if (*p == '/') p++;
13793                 emptycount = gameInfo.boardWidth - j;
13794                 while (emptycount--)
13795                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13796                 break;
13797 #if(BOARD_SIZE >= 10)
13798             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13799                 p++; emptycount=10;
13800                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13801                 while (emptycount--)
13802                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13803 #endif
13804             } else if (isdigit(*p)) {
13805                 emptycount = *p++ - '0';
13806                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13807                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13808                 while (emptycount--)
13809                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13810             } else if (*p == '+' || isalpha(*p)) {
13811                 if (j >= gameInfo.boardWidth) return FALSE;
13812                 if(*p=='+') {
13813                     piece = CharToPiece(*++p);
13814                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13815                     piece = (ChessSquare) (PROMOTED piece ); p++;
13816                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13817                 } else piece = CharToPiece(*p++);
13818
13819                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13820                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13821                     piece = (ChessSquare) (PROMOTED piece);
13822                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13823                     p++;
13824                 }
13825                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13826             } else {
13827                 return FALSE;
13828             }
13829         }
13830     }
13831     while (*p == '/' || *p == ' ') p++;
13832
13833     /* [HGM] look for Crazyhouse holdings here */
13834     while(*p==' ') p++;
13835     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13836         if(*p == '[') p++;
13837         if(*p == '-' ) *p++; /* empty holdings */ else {
13838             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13839             /* if we would allow FEN reading to set board size, we would   */
13840             /* have to add holdings and shift the board read so far here   */
13841             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13842                 *p++;
13843                 if((int) piece >= (int) BlackPawn ) {
13844                     i = (int)piece - (int)BlackPawn;
13845                     i = PieceToNumber((ChessSquare)i);
13846                     if( i >= gameInfo.holdingsSize ) return FALSE;
13847                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13848                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13849                 } else {
13850                     i = (int)piece - (int)WhitePawn;
13851                     i = PieceToNumber((ChessSquare)i);
13852                     if( i >= gameInfo.holdingsSize ) return FALSE;
13853                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13854                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13855                 }
13856             }
13857         }
13858         if(*p == ']') *p++;
13859     }
13860
13861     while(*p == ' ') p++;
13862
13863     /* Active color */
13864     switch (*p++) {
13865       case 'w':
13866         *blackPlaysFirst = FALSE;
13867         break;
13868       case 'b': 
13869         *blackPlaysFirst = TRUE;
13870         break;
13871       default:
13872         return FALSE;
13873     }
13874
13875     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13876     /* return the extra info in global variiables             */
13877
13878     /* set defaults in case FEN is incomplete */
13879     FENepStatus = EP_UNKNOWN;
13880     for(i=0; i<nrCastlingRights; i++ ) {
13881         FENcastlingRights[i] =
13882             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13883     }   /* assume possible unless obviously impossible */
13884     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13885     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13886     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13887     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13888     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13889     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13890     FENrulePlies = 0;
13891
13892     while(*p==' ') p++;
13893     if(nrCastlingRights) {
13894       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13895           /* castling indicator present, so default becomes no castlings */
13896           for(i=0; i<nrCastlingRights; i++ ) {
13897                  FENcastlingRights[i] = -1;
13898           }
13899       }
13900       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13901              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13902              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13903              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13904         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13905
13906         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13907             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13908             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13909         }
13910         switch(c) {
13911           case'K':
13912               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13913               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13914               FENcastlingRights[2] = whiteKingFile;
13915               break;
13916           case'Q':
13917               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13918               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13919               FENcastlingRights[2] = whiteKingFile;
13920               break;
13921           case'k':
13922               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13923               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13924               FENcastlingRights[5] = blackKingFile;
13925               break;
13926           case'q':
13927               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13928               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13929               FENcastlingRights[5] = blackKingFile;
13930           case '-':
13931               break;
13932           default: /* FRC castlings */
13933               if(c >= 'a') { /* black rights */
13934                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13935                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13936                   if(i == BOARD_RGHT) break;
13937                   FENcastlingRights[5] = i;
13938                   c -= AAA;
13939                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13940                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13941                   if(c > i)
13942                       FENcastlingRights[3] = c;
13943                   else
13944                       FENcastlingRights[4] = c;
13945               } else { /* white rights */
13946                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13947                     if(board[0][i] == WhiteKing) break;
13948                   if(i == BOARD_RGHT) break;
13949                   FENcastlingRights[2] = i;
13950                   c -= AAA - 'a' + 'A';
13951                   if(board[0][c] >= WhiteKing) break;
13952                   if(c > i)
13953                       FENcastlingRights[0] = c;
13954                   else
13955                       FENcastlingRights[1] = c;
13956               }
13957         }
13958       }
13959     if (appData.debugMode) {
13960         fprintf(debugFP, "FEN castling rights:");
13961         for(i=0; i<nrCastlingRights; i++)
13962         fprintf(debugFP, " %d", FENcastlingRights[i]);
13963         fprintf(debugFP, "\n");
13964     }
13965
13966       while(*p==' ') p++;
13967     }
13968
13969     /* read e.p. field in games that know e.p. capture */
13970     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13971        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13972       if(*p=='-') {
13973         p++; FENepStatus = EP_NONE;
13974       } else {
13975          char c = *p++ - AAA;
13976
13977          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13978          if(*p >= '0' && *p <='9') *p++;
13979          FENepStatus = c;
13980       }
13981     }
13982
13983
13984     if(sscanf(p, "%d", &i) == 1) {
13985         FENrulePlies = i; /* 50-move ply counter */
13986         /* (The move number is still ignored)    */
13987     }
13988
13989     return TRUE;
13990 }
13991       
13992 void
13993 EditPositionPasteFEN(char *fen)
13994 {
13995   if (fen != NULL) {
13996     Board initial_position;
13997
13998     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13999       DisplayError(_("Bad FEN position in clipboard"), 0);
14000       return ;
14001     } else {
14002       int savedBlackPlaysFirst = blackPlaysFirst;
14003       EditPositionEvent();
14004       blackPlaysFirst = savedBlackPlaysFirst;
14005       CopyBoard(boards[0], initial_position);
14006           /* [HGM] copy FEN attributes as well */
14007           {   int i;
14008               initialRulePlies = FENrulePlies;
14009               epStatus[0] = FENepStatus;
14010               for( i=0; i<nrCastlingRights; i++ )
14011                   castlingRights[0][i] = FENcastlingRights[i];
14012           }
14013       EditPositionDone();
14014       DisplayBothClocks();
14015       DrawPosition(FALSE, boards[currentMove]);
14016     }
14017   }
14018 }
14019
14020 static char cseq[12] = "\\   ";
14021
14022 Boolean set_cont_sequence(char *new_seq)
14023 {
14024     int len;
14025     Boolean ret;
14026
14027     // handle bad attempts to set the sequence
14028         if (!new_seq)
14029                 return 0; // acceptable error - no debug
14030
14031     len = strlen(new_seq);
14032     ret = (len > 0) && (len < sizeof(cseq));
14033     if (ret)
14034         strcpy(cseq, new_seq);
14035     else if (appData.debugMode)
14036         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14037     return ret;
14038 }
14039
14040 /*
14041     reformat a source message so words don't cross the width boundary.  internal
14042     newlines are not removed.  returns the wrapped size (no null character unless
14043     included in source message).  If dest is NULL, only calculate the size required
14044     for the dest buffer.  lp argument indicats line position upon entry, and it's
14045     passed back upon exit.
14046 */
14047 int wrap(char *dest, char *src, int count, int width, int *lp)
14048 {
14049     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14050
14051     cseq_len = strlen(cseq);
14052     old_line = line = *lp;
14053     ansi = len = clen = 0;
14054
14055     for (i=0; i < count; i++)
14056     {
14057         if (src[i] == '\033')
14058             ansi = 1;
14059
14060         // if we hit the width, back up
14061         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14062         {
14063             // store i & len in case the word is too long
14064             old_i = i, old_len = len;
14065
14066             // find the end of the last word
14067             while (i && src[i] != ' ' && src[i] != '\n')
14068             {
14069                 i--;
14070                 len--;
14071             }
14072
14073             // word too long?  restore i & len before splitting it
14074             if ((old_i-i+clen) >= width)
14075             {
14076                 i = old_i;
14077                 len = old_len;
14078             }
14079
14080             // extra space?
14081             if (i && src[i-1] == ' ')
14082                 len--;
14083
14084             if (src[i] != ' ' && src[i] != '\n')
14085             {
14086                 i--;
14087                 if (len)
14088                     len--;
14089             }
14090
14091             // now append the newline and continuation sequence
14092             if (dest)
14093                 dest[len] = '\n';
14094             len++;
14095             if (dest)
14096                 strncpy(dest+len, cseq, cseq_len);
14097             len += cseq_len;
14098             line = cseq_len;
14099             clen = cseq_len;
14100             continue;
14101         }
14102
14103         if (dest)
14104             dest[len] = src[i];
14105         len++;
14106         if (!ansi)
14107             line++;
14108         if (src[i] == '\n')
14109             line = 0;
14110         if (src[i] == 'm')
14111             ansi = 0;
14112     }
14113     if (dest && appData.debugMode)
14114     {
14115         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14116             count, width, line, len, *lp);
14117         show_bytes(debugFP, src, count);
14118         fprintf(debugFP, "\ndest: ");
14119         show_bytes(debugFP, dest, len);
14120         fprintf(debugFP, "\n");
14121     }
14122     *lp = dest ? line : old_line;
14123
14124     return len;
14125 }