06796c16e9ab288c23c977e6e1caee8abadb52b2
[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     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     if (appData.matchGames > 0) {
1046         appData.matchMode = TRUE;
1047     } else if (appData.matchMode) {
1048         appData.matchGames = 1;
1049     }
1050     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051         appData.matchGames = appData.sameColorGames;
1052     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055     }
1056     Reset(TRUE, FALSE);
1057     if (appData.noChessProgram || first.protocolVersion == 1) {
1058       InitBackEnd3();
1059     } else {
1060       /* kludge: allow timeout for initial "feature" commands */
1061       FreezeUI();
1062       DisplayMessage("", _("Starting chess program"));
1063       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1064     }
1065 }
1066
1067 void
1068 InitBackEnd3 P((void))
1069 {
1070     GameMode initialMode;
1071     char buf[MSG_SIZ];
1072     int err;
1073
1074     InitChessProgram(&first, startedFromSetupPosition);
1075
1076
1077     if (appData.icsActive) {
1078 #ifdef WIN32
1079         /* [DM] Make a console window if needed [HGM] merged ifs */
1080         ConsoleCreate(); 
1081 #endif
1082         err = establish();
1083         if (err != 0) {
1084             if (*appData.icsCommPort != NULLCHAR) {
1085                 sprintf(buf, _("Could not open comm port %s"),  
1086                         appData.icsCommPort);
1087             } else {
1088                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1089                         appData.icsHost, appData.icsPort);
1090             }
1091             DisplayFatalError(buf, err, 1);
1092             return;
1093         }
1094         SetICSMode();
1095         telnetISR =
1096           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097         fromUserISR =
1098           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099     } else if (appData.noChessProgram) {
1100         SetNCPMode();
1101     } else {
1102         SetGNUMode();
1103     }
1104
1105     if (*appData.cmailGameName != NULLCHAR) {
1106         SetCmailMode();
1107         OpenLoopback(&cmailPR);
1108         cmailISR =
1109           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1110     }
1111     
1112     ThawUI();
1113     DisplayMessage("", "");
1114     if (StrCaseCmp(appData.initialMode, "") == 0) {
1115       initialMode = BeginningOfGame;
1116     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117       initialMode = TwoMachinesPlay;
1118     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119       initialMode = AnalyzeFile; 
1120     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121       initialMode = AnalyzeMode;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123       initialMode = MachinePlaysWhite;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125       initialMode = MachinePlaysBlack;
1126     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127       initialMode = EditGame;
1128     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129       initialMode = EditPosition;
1130     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131       initialMode = Training;
1132     } else {
1133       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134       DisplayFatalError(buf, 0, 2);
1135       return;
1136     }
1137
1138     if (appData.matchMode) {
1139         /* Set up machine vs. machine match */
1140         if (appData.noChessProgram) {
1141             DisplayFatalError(_("Can't have a match with no chess programs"),
1142                               0, 2);
1143             return;
1144         }
1145         matchMode = TRUE;
1146         matchGame = 1;
1147         if (*appData.loadGameFile != NULLCHAR) {
1148             int index = appData.loadGameIndex; // [HGM] autoinc
1149             if(index<0) lastIndex = index = 1;
1150             if (!LoadGameFromFile(appData.loadGameFile,
1151                                   index,
1152                                   appData.loadGameFile, FALSE)) {
1153                 DisplayFatalError(_("Bad game file"), 0, 1);
1154                 return;
1155             }
1156         } else if (*appData.loadPositionFile != NULLCHAR) {
1157             int index = appData.loadPositionIndex; // [HGM] autoinc
1158             if(index<0) lastIndex = index = 1;
1159             if (!LoadPositionFromFile(appData.loadPositionFile,
1160                                       index,
1161                                       appData.loadPositionFile)) {
1162                 DisplayFatalError(_("Bad position file"), 0, 1);
1163                 return;
1164             }
1165         }
1166         TwoMachinesEvent();
1167     } else if (*appData.cmailGameName != NULLCHAR) {
1168         /* Set up cmail mode */
1169         ReloadCmailMsgEvent(TRUE);
1170     } else {
1171         /* Set up other modes */
1172         if (initialMode == AnalyzeFile) {
1173           if (*appData.loadGameFile == NULLCHAR) {
1174             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1175             return;
1176           }
1177         }
1178         if (*appData.loadGameFile != NULLCHAR) {
1179             (void) LoadGameFromFile(appData.loadGameFile,
1180                                     appData.loadGameIndex,
1181                                     appData.loadGameFile, TRUE);
1182         } else if (*appData.loadPositionFile != NULLCHAR) {
1183             (void) LoadPositionFromFile(appData.loadPositionFile,
1184                                         appData.loadPositionIndex,
1185                                         appData.loadPositionFile);
1186             /* [HGM] try to make self-starting even after FEN load */
1187             /* to allow automatic setup of fairy variants with wtm */
1188             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189                 gameMode = BeginningOfGame;
1190                 setboardSpoiledMachineBlack = 1;
1191             }
1192             /* [HGM] loadPos: make that every new game uses the setup */
1193             /* from file as long as we do not switch variant          */
1194             if(!blackPlaysFirst) { int i;
1195                 startedFromPositionFile = TRUE;
1196                 CopyBoard(filePosition, boards[0]);
1197                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198             }
1199         }
1200         if (initialMode == AnalyzeMode) {
1201           if (appData.noChessProgram) {
1202             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203             return;
1204           }
1205           if (appData.icsActive) {
1206             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1207             return;
1208           }
1209           AnalyzeModeEvent();
1210         } else if (initialMode == AnalyzeFile) {
1211           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212           ShowThinkingEvent();
1213           AnalyzeFileEvent();
1214           AnalysisPeriodicEvent(1);
1215         } else if (initialMode == MachinePlaysWhite) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1218                               0, 2);
1219             return;
1220           }
1221           if (appData.icsActive) {
1222             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1223                               0, 2);
1224             return;
1225           }
1226           MachineWhiteEvent();
1227         } else if (initialMode == MachinePlaysBlack) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineBlackEvent();
1239         } else if (initialMode == TwoMachinesPlay) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           TwoMachinesEvent();
1251         } else if (initialMode == EditGame) {
1252           EditGameEvent();
1253         } else if (initialMode == EditPosition) {
1254           EditPositionEvent();
1255         } else if (initialMode == Training) {
1256           if (*appData.loadGameFile == NULLCHAR) {
1257             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1258             return;
1259           }
1260           TrainingEvent();
1261         }
1262     }
1263 }
1264
1265 /*
1266  * Establish will establish a contact to a remote host.port.
1267  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268  *  used to talk to the host.
1269  * Returns 0 if okay, error code if not.
1270  */
1271 int
1272 establish()
1273 {
1274     char buf[MSG_SIZ];
1275
1276     if (*appData.icsCommPort != NULLCHAR) {
1277         /* Talk to the host through a serial comm port */
1278         return OpenCommPort(appData.icsCommPort, &icsPR);
1279
1280     } else if (*appData.gateway != NULLCHAR) {
1281         if (*appData.remoteShell == NULLCHAR) {
1282             /* Use the rcmd protocol to run telnet program on a gateway host */
1283             snprintf(buf, sizeof(buf), "%s %s %s",
1284                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1285             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286
1287         } else {
1288             /* Use the rsh program to run telnet program on a gateway host */
1289             if (*appData.remoteUser == NULLCHAR) {
1290                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291                         appData.gateway, appData.telnetProgram,
1292                         appData.icsHost, appData.icsPort);
1293             } else {
1294                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295                         appData.remoteShell, appData.gateway, 
1296                         appData.remoteUser, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             }
1299             return StartChildProcess(buf, "", &icsPR);
1300
1301         }
1302     } else if (appData.useTelnet) {
1303         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304
1305     } else {
1306         /* TCP socket interface differs somewhat between
1307            Unix and NT; handle details in the front end.
1308            */
1309         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1310     }
1311 }
1312
1313 void
1314 show_bytes(fp, buf, count)
1315      FILE *fp;
1316      char *buf;
1317      int count;
1318 {
1319     while (count--) {
1320         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321             fprintf(fp, "\\%03o", *buf & 0xff);
1322         } else {
1323             putc(*buf, fp);
1324         }
1325         buf++;
1326     }
1327     fflush(fp);
1328 }
1329
1330 /* Returns an errno value */
1331 int
1332 OutputMaybeTelnet(pr, message, count, outError)
1333      ProcRef pr;
1334      char *message;
1335      int count;
1336      int *outError;
1337 {
1338     char buf[8192], *p, *q, *buflim;
1339     int left, newcount, outcount;
1340
1341     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342         *appData.gateway != NULLCHAR) {
1343         if (appData.debugMode) {
1344             fprintf(debugFP, ">ICS: ");
1345             show_bytes(debugFP, message, count);
1346             fprintf(debugFP, "\n");
1347         }
1348         return OutputToProcess(pr, message, count, outError);
1349     }
1350
1351     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1352     p = message;
1353     q = buf;
1354     left = count;
1355     newcount = 0;
1356     while (left) {
1357         if (q >= buflim) {
1358             if (appData.debugMode) {
1359                 fprintf(debugFP, ">ICS: ");
1360                 show_bytes(debugFP, buf, newcount);
1361                 fprintf(debugFP, "\n");
1362             }
1363             outcount = OutputToProcess(pr, buf, newcount, outError);
1364             if (outcount < newcount) return -1; /* to be sure */
1365             q = buf;
1366             newcount = 0;
1367         }
1368         if (*p == '\n') {
1369             *q++ = '\r';
1370             newcount++;
1371         } else if (((unsigned char) *p) == TN_IAC) {
1372             *q++ = (char) TN_IAC;
1373             newcount ++;
1374         }
1375         *q++ = *p++;
1376         newcount++;
1377         left--;
1378     }
1379     if (appData.debugMode) {
1380         fprintf(debugFP, ">ICS: ");
1381         show_bytes(debugFP, buf, newcount);
1382         fprintf(debugFP, "\n");
1383     }
1384     outcount = OutputToProcess(pr, buf, newcount, outError);
1385     if (outcount < newcount) return -1; /* to be sure */
1386     return count;
1387 }
1388
1389 void
1390 read_from_player(isr, closure, message, count, error)
1391      InputSourceRef isr;
1392      VOIDSTAR closure;
1393      char *message;
1394      int count;
1395      int error;
1396 {
1397     int outError, outCount;
1398     static int gotEof = 0;
1399
1400     /* Pass data read from player on to ICS */
1401     if (count > 0) {
1402         gotEof = 0;
1403         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404         if (outCount < count) {
1405             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1406         }
1407     } else if (count < 0) {
1408         RemoveInputSource(isr);
1409         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410     } else if (gotEof++ > 0) {
1411         RemoveInputSource(isr);
1412         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1413     }
1414 }
1415
1416 void
1417 KeepAlive()
1418 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419     SendToICS("date\n");
1420     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 }
1422
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1425 {
1426     char buffer[MSG_SIZ];
1427     va_list args;
1428
1429     va_start(args, format);
1430     vsnprintf(buffer, sizeof(buffer), format, args);
1431     buffer[sizeof(buffer)-1] = '\0';
1432     SendToICS(buffer);
1433     va_end(args);
1434 }
1435
1436 void
1437 SendToICS(s)
1438      char *s;
1439 {
1440     int count, outCount, outError;
1441
1442     if (icsPR == NULL) return;
1443
1444     count = strlen(s);
1445     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1446     if (outCount < count) {
1447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448     }
1449 }
1450
1451 /* This is used for sending logon scripts to the ICS. Sending
1452    without a delay causes problems when using timestamp on ICC
1453    (at least on my machine). */
1454 void
1455 SendToICSDelayed(s,msdelay)
1456      char *s;
1457      long msdelay;
1458 {
1459     int count, outCount, outError;
1460
1461     if (icsPR == NULL) return;
1462
1463     count = strlen(s);
1464     if (appData.debugMode) {
1465         fprintf(debugFP, ">ICS: ");
1466         show_bytes(debugFP, s, count);
1467         fprintf(debugFP, "\n");
1468     }
1469     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1470                                       msdelay);
1471     if (outCount < count) {
1472         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1473     }
1474 }
1475
1476
1477 /* Remove all highlighting escape sequences in s
1478    Also deletes any suffix starting with '(' 
1479    */
1480 char *
1481 StripHighlightAndTitle(s)
1482      char *s;
1483 {
1484     static char retbuf[MSG_SIZ];
1485     char *p = retbuf;
1486
1487     while (*s != NULLCHAR) {
1488         while (*s == '\033') {
1489             while (*s != NULLCHAR && !isalpha(*s)) s++;
1490             if (*s != NULLCHAR) s++;
1491         }
1492         while (*s != NULLCHAR && *s != '\033') {
1493             if (*s == '(' || *s == '[') {
1494                 *p = NULLCHAR;
1495                 return retbuf;
1496             }
1497             *p++ = *s++;
1498         }
1499     }
1500     *p = NULLCHAR;
1501     return retbuf;
1502 }
1503
1504 /* Remove all highlighting escape sequences in s */
1505 char *
1506 StripHighlight(s)
1507      char *s;
1508 {
1509     static char retbuf[MSG_SIZ];
1510     char *p = retbuf;
1511
1512     while (*s != NULLCHAR) {
1513         while (*s == '\033') {
1514             while (*s != NULLCHAR && !isalpha(*s)) s++;
1515             if (*s != NULLCHAR) s++;
1516         }
1517         while (*s != NULLCHAR && *s != '\033') {
1518             *p++ = *s++;
1519         }
1520     }
1521     *p = NULLCHAR;
1522     return retbuf;
1523 }
1524
1525 char *variantNames[] = VARIANT_NAMES;
1526 char *
1527 VariantName(v)
1528      VariantClass v;
1529 {
1530     return variantNames[v];
1531 }
1532
1533
1534 /* Identify a variant from the strings the chess servers use or the
1535    PGN Variant tag names we use. */
1536 VariantClass
1537 StringToVariant(e)
1538      char *e;
1539 {
1540     char *p;
1541     int wnum = -1;
1542     VariantClass v = VariantNormal;
1543     int i, found = FALSE;
1544     char buf[MSG_SIZ];
1545
1546     if (!e) return v;
1547
1548     /* [HGM] skip over optional board-size prefixes */
1549     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1550         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1551         while( *e++ != '_');
1552     }
1553
1554     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1555         v = VariantNormal;
1556         found = TRUE;
1557     } else
1558     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1559       if (StrCaseStr(e, variantNames[i])) {
1560         v = (VariantClass) i;
1561         found = TRUE;
1562         break;
1563       }
1564     }
1565
1566     if (!found) {
1567       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1568           || StrCaseStr(e, "wild/fr") 
1569           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1570         v = VariantFischeRandom;
1571       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1572                  (i = 1, p = StrCaseStr(e, "w"))) {
1573         p += i;
1574         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1575         if (isdigit(*p)) {
1576           wnum = atoi(p);
1577         } else {
1578           wnum = -1;
1579         }
1580         switch (wnum) {
1581         case 0: /* FICS only, actually */
1582         case 1:
1583           /* Castling legal even if K starts on d-file */
1584           v = VariantWildCastle;
1585           break;
1586         case 2:
1587         case 3:
1588         case 4:
1589           /* Castling illegal even if K & R happen to start in
1590              normal positions. */
1591           v = VariantNoCastle;
1592           break;
1593         case 5:
1594         case 7:
1595         case 8:
1596         case 10:
1597         case 11:
1598         case 12:
1599         case 13:
1600         case 14:
1601         case 15:
1602         case 18:
1603         case 19:
1604           /* Castling legal iff K & R start in normal positions */
1605           v = VariantNormal;
1606           break;
1607         case 6:
1608         case 20:
1609         case 21:
1610           /* Special wilds for position setup; unclear what to do here */
1611           v = VariantLoadable;
1612           break;
1613         case 9:
1614           /* Bizarre ICC game */
1615           v = VariantTwoKings;
1616           break;
1617         case 16:
1618           v = VariantKriegspiel;
1619           break;
1620         case 17:
1621           v = VariantLosers;
1622           break;
1623         case 22:
1624           v = VariantFischeRandom;
1625           break;
1626         case 23:
1627           v = VariantCrazyhouse;
1628           break;
1629         case 24:
1630           v = VariantBughouse;
1631           break;
1632         case 25:
1633           v = Variant3Check;
1634           break;
1635         case 26:
1636           /* Not quite the same as FICS suicide! */
1637           v = VariantGiveaway;
1638           break;
1639         case 27:
1640           v = VariantAtomic;
1641           break;
1642         case 28:
1643           v = VariantShatranj;
1644           break;
1645
1646         /* Temporary names for future ICC types.  The name *will* change in 
1647            the next xboard/WinBoard release after ICC defines it. */
1648         case 29:
1649           v = Variant29;
1650           break;
1651         case 30:
1652           v = Variant30;
1653           break;
1654         case 31:
1655           v = Variant31;
1656           break;
1657         case 32:
1658           v = Variant32;
1659           break;
1660         case 33:
1661           v = Variant33;
1662           break;
1663         case 34:
1664           v = Variant34;
1665           break;
1666         case 35:
1667           v = Variant35;
1668           break;
1669         case 36:
1670           v = Variant36;
1671           break;
1672         case 37:
1673           v = VariantShogi;
1674           break;
1675         case 38:
1676           v = VariantXiangqi;
1677           break;
1678         case 39:
1679           v = VariantCourier;
1680           break;
1681         case 40:
1682           v = VariantGothic;
1683           break;
1684         case 41:
1685           v = VariantCapablanca;
1686           break;
1687         case 42:
1688           v = VariantKnightmate;
1689           break;
1690         case 43:
1691           v = VariantFairy;
1692           break;
1693         case 44:
1694           v = VariantCylinder;
1695           break;
1696         case 45:
1697           v = VariantFalcon;
1698           break;
1699         case 46:
1700           v = VariantCapaRandom;
1701           break;
1702         case 47:
1703           v = VariantBerolina;
1704           break;
1705         case 48:
1706           v = VariantJanus;
1707           break;
1708         case 49:
1709           v = VariantSuper;
1710           break;
1711         case 50:
1712           v = VariantGreat;
1713           break;
1714         case -1:
1715           /* Found "wild" or "w" in the string but no number;
1716              must assume it's normal chess. */
1717           v = VariantNormal;
1718           break;
1719         default:
1720           sprintf(buf, _("Unknown wild type %d"), wnum);
1721           DisplayError(buf, 0);
1722           v = VariantUnknown;
1723           break;
1724         }
1725       }
1726     }
1727     if (appData.debugMode) {
1728       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1729               e, wnum, VariantName(v));
1730     }
1731     return v;
1732 }
1733
1734 static int leftover_start = 0, leftover_len = 0;
1735 char star_match[STAR_MATCH_N][MSG_SIZ];
1736
1737 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1738    advance *index beyond it, and set leftover_start to the new value of
1739    *index; else return FALSE.  If pattern contains the character '*', it
1740    matches any sequence of characters not containing '\r', '\n', or the
1741    character following the '*' (if any), and the matched sequence(s) are
1742    copied into star_match.
1743    */
1744 int
1745 looking_at(buf, index, pattern)
1746      char *buf;
1747      int *index;
1748      char *pattern;
1749 {
1750     char *bufp = &buf[*index], *patternp = pattern;
1751     int star_count = 0;
1752     char *matchp = star_match[0];
1753     
1754     for (;;) {
1755         if (*patternp == NULLCHAR) {
1756             *index = leftover_start = bufp - buf;
1757             *matchp = NULLCHAR;
1758             return TRUE;
1759         }
1760         if (*bufp == NULLCHAR) return FALSE;
1761         if (*patternp == '*') {
1762             if (*bufp == *(patternp + 1)) {
1763                 *matchp = NULLCHAR;
1764                 matchp = star_match[++star_count];
1765                 patternp += 2;
1766                 bufp++;
1767                 continue;
1768             } else if (*bufp == '\n' || *bufp == '\r') {
1769                 patternp++;
1770                 if (*patternp == NULLCHAR)
1771                   continue;
1772                 else
1773                   return FALSE;
1774             } else {
1775                 *matchp++ = *bufp++;
1776                 continue;
1777             }
1778         }
1779         if (*patternp != *bufp) return FALSE;
1780         patternp++;
1781         bufp++;
1782     }
1783 }
1784
1785 void
1786 SendToPlayer(data, length)
1787      char *data;
1788      int length;
1789 {
1790     int error, outCount;
1791     outCount = OutputToProcess(NoProc, data, length, &error);
1792     if (outCount < length) {
1793         DisplayFatalError(_("Error writing to display"), error, 1);
1794     }
1795 }
1796
1797 void
1798 PackHolding(packed, holding)
1799      char packed[];
1800      char *holding;
1801 {
1802     char *p = holding;
1803     char *q = packed;
1804     int runlength = 0;
1805     int curr = 9999;
1806     do {
1807         if (*p == curr) {
1808             runlength++;
1809         } else {
1810             switch (runlength) {
1811               case 0:
1812                 break;
1813               case 1:
1814                 *q++ = curr;
1815                 break;
1816               case 2:
1817                 *q++ = curr;
1818                 *q++ = curr;
1819                 break;
1820               default:
1821                 sprintf(q, "%d", runlength);
1822                 while (*q) q++;
1823                 *q++ = curr;
1824                 break;
1825             }
1826             runlength = 1;
1827             curr = *p;
1828         }
1829     } while (*p++);
1830     *q = NULLCHAR;
1831 }
1832
1833 /* Telnet protocol requests from the front end */
1834 void
1835 TelnetRequest(ddww, option)
1836      unsigned char ddww, option;
1837 {
1838     unsigned char msg[3];
1839     int outCount, outError;
1840
1841     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1842
1843     if (appData.debugMode) {
1844         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1845         switch (ddww) {
1846           case TN_DO:
1847             ddwwStr = "DO";
1848             break;
1849           case TN_DONT:
1850             ddwwStr = "DONT";
1851             break;
1852           case TN_WILL:
1853             ddwwStr = "WILL";
1854             break;
1855           case TN_WONT:
1856             ddwwStr = "WONT";
1857             break;
1858           default:
1859             ddwwStr = buf1;
1860             sprintf(buf1, "%d", ddww);
1861             break;
1862         }
1863         switch (option) {
1864           case TN_ECHO:
1865             optionStr = "ECHO";
1866             break;
1867           default:
1868             optionStr = buf2;
1869             sprintf(buf2, "%d", option);
1870             break;
1871         }
1872         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1873     }
1874     msg[0] = TN_IAC;
1875     msg[1] = ddww;
1876     msg[2] = option;
1877     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1878     if (outCount < 3) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883 void
1884 DoEcho()
1885 {
1886     if (!appData.icsActive) return;
1887     TelnetRequest(TN_DO, TN_ECHO);
1888 }
1889
1890 void
1891 DontEcho()
1892 {
1893     if (!appData.icsActive) return;
1894     TelnetRequest(TN_DONT, TN_ECHO);
1895 }
1896
1897 void
1898 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1899 {
1900     /* put the holdings sent to us by the server on the board holdings area */
1901     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1902     char p;
1903     ChessSquare piece;
1904
1905     if(gameInfo.holdingsWidth < 2)  return;
1906
1907     if( (int)lowestPiece >= BlackPawn ) {
1908         holdingsColumn = 0;
1909         countsColumn = 1;
1910         holdingsStartRow = BOARD_HEIGHT-1;
1911         direction = -1;
1912     } else {
1913         holdingsColumn = BOARD_WIDTH-1;
1914         countsColumn = BOARD_WIDTH-2;
1915         holdingsStartRow = 0;
1916         direction = 1;
1917     }
1918
1919     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920         board[i][holdingsColumn] = EmptySquare;
1921         board[i][countsColumn]   = (ChessSquare) 0;
1922     }
1923     while( (p=*holdings++) != NULLCHAR ) {
1924         piece = CharToPiece( ToUpper(p) );
1925         if(piece == EmptySquare) continue;
1926         /*j = (int) piece - (int) WhitePawn;*/
1927         j = PieceToNumber(piece);
1928         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929         if(j < 0) continue;               /* should not happen */
1930         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932         board[holdingsStartRow+j*direction][countsColumn]++;
1933     }
1934
1935 }
1936
1937
1938 void
1939 VariantSwitch(Board board, VariantClass newVariant)
1940 {
1941    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1942
1943    startedFromPositionFile = FALSE;
1944    if(gameInfo.variant == newVariant) return;
1945
1946    /* [HGM] This routine is called each time an assignment is made to
1947     * gameInfo.variant during a game, to make sure the board sizes
1948     * are set to match the new variant. If that means adding or deleting
1949     * holdings, we shift the playing board accordingly
1950     * This kludge is needed because in ICS observe mode, we get boards
1951     * of an ongoing game without knowing the variant, and learn about the
1952     * latter only later. This can be because of the move list we requested,
1953     * in which case the game history is refilled from the beginning anyway,
1954     * but also when receiving holdings of a crazyhouse game. In the latter
1955     * case we want to add those holdings to the already received position.
1956     */
1957
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        return;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2014    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2015    
2016    DrawPosition(TRUE, boards[currentMove]);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     static int cmatch = 0; // continuation sequence match
2060     char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2082     }
2083     if (count > 0) {
2084         /* If last read ended with a partial line that we couldn't parse,
2085            prepend it to the new read and try again. */
2086         if (leftover_len > 0) {
2087             for (i=0; i<leftover_len; i++)
2088               buf[i] = buf[leftover_start + i];
2089         }
2090
2091     /* copy new characters into the buffer */
2092     bp = buf + leftover_len;
2093     buf_len=leftover_len;
2094     for (i=0; i<count; i++)
2095     {
2096         // ignore these
2097         if (data[i] == '\r')
2098             continue;
2099
2100         // join lines split by ICS?
2101         if (!appData.noJoin)
2102         {
2103             /*
2104                 Joining just consists of finding matches against the
2105                 continuation sequence, and discarding that sequence
2106                 if found instead of copying it.  So, until a match
2107                 fails, there's nothing to do since it might be the
2108                 complete sequence, and thus, something we don't want
2109                 copied.
2110             */
2111             if (data[i] == cont_seq[cmatch])
2112             {
2113                 cmatch++;
2114                 if (cmatch == strlen(cont_seq))
2115                 {
2116                     cmatch = 0; // complete match.  just reset the counter
2117
2118                     /*
2119                         it's possible for the ICS to not include the space
2120                         at the end of the last word, making our [correct]
2121                         join operation fuse two separate words.  the server
2122                         does this when the space occurs at the width setting.
2123                     */
2124                     if (!buf_len || buf[buf_len-1] != ' ')
2125                     {
2126                         *bp++ = ' ';
2127                         buf_len++;
2128                     }
2129                 }
2130                 continue;
2131             }
2132             else if (cmatch)
2133             {
2134                 /*
2135                     match failed, so we have to copy what matched before
2136                     falling through and copying this character.  In reality,
2137                     this will only ever be just the newline character, but
2138                     it doesn't hurt to be precise.
2139                 */
2140                 strncpy(bp, cont_seq, cmatch);
2141                 bp += cmatch;
2142                 buf_len += cmatch;
2143                 cmatch = 0;
2144             }
2145         }
2146
2147         // copy this char
2148         *bp++ = data[i];
2149         buf_len++;
2150     }
2151
2152         buf[buf_len] = NULLCHAR;
2153         next_out = leftover_len;
2154         leftover_start = 0;
2155         
2156         i = 0;
2157         while (i < buf_len) {
2158             /* Deal with part of the TELNET option negotiation
2159                protocol.  We refuse to do anything beyond the
2160                defaults, except that we allow the WILL ECHO option,
2161                which ICS uses to turn off password echoing when we are
2162                directly connected to it.  We reject this option
2163                if localLineEditing mode is on (always on in xboard)
2164                and we are talking to port 23, which might be a real
2165                telnet server that will try to keep WILL ECHO on permanently.
2166              */
2167             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169                 unsigned char option;
2170                 oldi = i;
2171                 switch ((unsigned char) buf[++i]) {
2172                   case TN_WILL:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<WILL ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       case TN_ECHO:
2177                         if (appData.debugMode)
2178                           fprintf(debugFP, "ECHO ");
2179                         /* Reply only if this is a change, according
2180                            to the protocol rules. */
2181                         if (remoteEchoOption) break;
2182                         if (appData.localLineEditing &&
2183                             atoi(appData.icsPort) == TN_PORT) {
2184                             TelnetRequest(TN_DONT, TN_ECHO);
2185                         } else {
2186                             EchoOff();
2187                             TelnetRequest(TN_DO, TN_ECHO);
2188                             remoteEchoOption = TRUE;
2189                         }
2190                         break;
2191                       default:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "%d ", option);
2194                         /* Whatever this is, we don't want it. */
2195                         TelnetRequest(TN_DONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_WONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<WONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       case TN_ECHO:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "ECHO ");
2206                         /* Reply only if this is a change, according
2207                            to the protocol rules. */
2208                         if (!remoteEchoOption) break;
2209                         EchoOn();
2210                         TelnetRequest(TN_DONT, TN_ECHO);
2211                         remoteEchoOption = FALSE;
2212                         break;
2213                       default:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "%d ", (unsigned char) option);
2216                         /* Whatever this is, it must already be turned
2217                            off, because we never agree to turn on
2218                            anything non-default, so according to the
2219                            protocol rules, we don't reply. */
2220                         break;
2221                     }
2222                     break;
2223                   case TN_DO:
2224                     if (appData.debugMode)
2225                       fprintf(debugFP, "\n<DO ");
2226                     switch (option = (unsigned char) buf[++i]) {
2227                       default:
2228                         /* Whatever this is, we refuse to do it. */
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         TelnetRequest(TN_WONT, option);
2232                         break;
2233                     }
2234                     break;
2235                   case TN_DONT:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<DONT ");
2238                     switch (option = (unsigned char) buf[++i]) {
2239                       default:
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "%d ", option);
2242                         /* Whatever this is, we are already not doing
2243                            it, because we never agree to do anything
2244                            non-default, so according to the protocol
2245                            rules, we don't reply. */
2246                         break;
2247                     }
2248                     break;
2249                   case TN_IAC:
2250                     if (appData.debugMode)
2251                       fprintf(debugFP, "\n<IAC ");
2252                     /* Doubled IAC; pass it through */
2253                     i--;
2254                     break;
2255                   default:
2256                     if (appData.debugMode)
2257                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258                     /* Drop all other telnet commands on the floor */
2259                     break;
2260                 }
2261                 if (oldi > next_out)
2262                   SendToPlayer(&buf[next_out], oldi - next_out);
2263                 if (++i > next_out)
2264                   next_out = i;
2265                 continue;
2266             }
2267                 
2268             /* OK, this at least will *usually* work */
2269             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2270                 loggedOn = TRUE;
2271             }
2272             
2273             if (loggedOn && !intfSet) {
2274                 if (ics_type == ICS_ICC) {
2275                   sprintf(str,
2276                           "/set-quietly interface %s\n/set-quietly style 12\n",
2277                           programVersion);
2278                 } else if (ics_type == ICS_CHESSNET) {
2279                   sprintf(str, "/style 12\n");
2280                 } else {
2281                   strcpy(str, "alias $ @\n$set interface ");
2282                   strcat(str, programVersion);
2283                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2284 #ifdef WIN32
2285                   strcat(str, "$iset nohighlight 1\n");
2286 #endif
2287                   strcat(str, "$iset lock 1\n$style 12\n");
2288                 }
2289                 SendToICS(str);
2290                 NotifyFrontendLogin();
2291                 intfSet = TRUE;
2292             }
2293
2294             if (started == STARTED_COMMENT) {
2295                 /* Accumulate characters in comment */
2296                 parse[parse_pos++] = buf[i];
2297                 if (buf[i] == '\n') {
2298                     parse[parse_pos] = NULLCHAR;
2299                     if(chattingPartner>=0) {
2300                         char mess[MSG_SIZ];
2301                         sprintf(mess, "%s%s", talker, parse);
2302                         OutputChatMessage(chattingPartner, mess);
2303                         chattingPartner = -1;
2304                     } else
2305                     if(!suppressKibitz) // [HGM] kibitz
2306                         AppendComment(forwardMostMove, StripHighlight(parse));
2307                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308                         int nrDigit = 0, nrAlph = 0, i;
2309                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311                         parse[parse_pos] = NULLCHAR;
2312                         // try to be smart: if it does not look like search info, it should go to
2313                         // ICS interaction window after all, not to engine-output window.
2314                         for(i=0; i<parse_pos; i++) { // count letters and digits
2315                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2317                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2318                         }
2319                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320                             int depth=0; float score;
2321                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323                                 pvInfoList[forwardMostMove-1].depth = depth;
2324                                 pvInfoList[forwardMostMove-1].score = 100*score;
2325                             }
2326                             OutputKibitz(suppressKibitz, parse);
2327                         } else {
2328                             char tmp[MSG_SIZ];
2329                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330                             SendToPlayer(tmp, strlen(tmp));
2331                         }
2332                     }
2333                     started = STARTED_NONE;
2334                 } else {
2335                     /* Don't match patterns against characters in chatter */
2336                     i++;
2337                     continue;
2338                 }
2339             }
2340             if (started == STARTED_CHATTER) {
2341                 if (buf[i] != '\n') {
2342                     /* Don't match patterns against characters in chatter */
2343                     i++;
2344                     continue;
2345                 }
2346                 started = STARTED_NONE;
2347             }
2348
2349             /* Kludge to deal with rcmd protocol */
2350             if (firstTime && looking_at(buf, &i, "\001*")) {
2351                 DisplayFatalError(&buf[1], 0, 1);
2352                 continue;
2353             } else {
2354                 firstTime = FALSE;
2355             }
2356
2357             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2358                 ics_type = ICS_ICC;
2359                 ics_prefix = "/";
2360                 if (appData.debugMode)
2361                   fprintf(debugFP, "ics_type %d\n", ics_type);
2362                 continue;
2363             }
2364             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365                 ics_type = ICS_FICS;
2366                 ics_prefix = "$";
2367                 if (appData.debugMode)
2368                   fprintf(debugFP, "ics_type %d\n", ics_type);
2369                 continue;
2370             }
2371             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372                 ics_type = ICS_CHESSNET;
2373                 ics_prefix = "/";
2374                 if (appData.debugMode)
2375                   fprintf(debugFP, "ics_type %d\n", ics_type);
2376                 continue;
2377             }
2378
2379             if (!loggedOn &&
2380                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2382                  looking_at(buf, &i, "will be \"*\""))) {
2383               strcpy(ics_handle, star_match[0]);
2384               continue;
2385             }
2386
2387             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2388               char buf[MSG_SIZ];
2389               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390               DisplayIcsInteractionTitle(buf);
2391               have_set_title = TRUE;
2392             }
2393
2394             /* skip finger notes */
2395             if (started == STARTED_NONE &&
2396                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397                  (buf[i] == '1' && buf[i+1] == '0')) &&
2398                 buf[i+2] == ':' && buf[i+3] == ' ') {
2399               started = STARTED_CHATTER;
2400               i += 3;
2401               continue;
2402             }
2403
2404             /* skip formula vars */
2405             if (started == STARTED_NONE &&
2406                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407               started = STARTED_CHATTER;
2408               i += 3;
2409               continue;
2410             }
2411
2412             oldi = i;
2413             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414             if (appData.autoKibitz && started == STARTED_NONE && 
2415                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2416                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417                 if(looking_at(buf, &i, "* kibitzes: ") &&
2418                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2419                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2420                         suppressKibitz = TRUE;
2421                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422                                 && (gameMode == IcsPlayingWhite)) ||
2423                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2425                             started = STARTED_CHATTER; // own kibitz we simply discard
2426                         else {
2427                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428                             parse_pos = 0; parse[0] = NULLCHAR;
2429                             savingComment = TRUE;
2430                             suppressKibitz = gameMode != IcsObserving ? 2 :
2431                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2432                         } 
2433                         continue;
2434                 } else
2435                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436                     started = STARTED_CHATTER;
2437                     suppressKibitz = TRUE;
2438                 }
2439             } // [HGM] kibitz: end of patch
2440
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2442
2443             // [HGM] chat: intercept tells by users for which we have an open chat window
2444             channel = -1;
2445             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2446                                            looking_at(buf, &i, "* whispers:") ||
2447                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2449                 int p;
2450                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451                 chattingPartner = -1;
2452
2453                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454                 for(p=0; p<MAX_CHAT; p++) {
2455                     if(channel == atoi(chatPartner[p])) {
2456                     talker[0] = '['; strcat(talker, "]");
2457                     chattingPartner = p; break;
2458                     }
2459                 } else
2460                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461                 for(p=0; p<MAX_CHAT; p++) {
2462                     if(!strcmp("WHISPER", chatPartner[p])) {
2463                         talker[0] = '['; strcat(talker, "]");
2464                         chattingPartner = p; break;
2465                     }
2466                 }
2467                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2469                     talker[0] = 0;
2470                     chattingPartner = p; break;
2471                 }
2472                 if(chattingPartner<0) i = oldi; else {
2473                     started = STARTED_COMMENT;
2474                     parse_pos = 0; parse[0] = NULLCHAR;
2475                     savingComment = TRUE;
2476                     suppressKibitz = TRUE;
2477                 }
2478             } // [HGM] chat: end of patch
2479
2480             if (appData.zippyTalk || appData.zippyPlay) {
2481                 /* [DM] Backup address for color zippy lines */
2482                 backup = i;
2483 #if ZIPPY
2484        #ifdef WIN32
2485                if (loggedOn == TRUE)
2486                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2488        #else
2489                 if (ZippyControl(buf, &i) ||
2490                     ZippyConverse(buf, &i) ||
2491                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2492                       loggedOn = TRUE;
2493                       if (!appData.colorize) continue;
2494                 }
2495        #endif
2496 #endif
2497             } // [DM] 'else { ' deleted
2498                 if (
2499                     /* Regular tells and says */
2500                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2502                     looking_at(buf, &i, "* says: ") ||
2503                     /* Don't color "message" or "messages" output */
2504                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505                     looking_at(buf, &i, "*. * at *:*: ") ||
2506                     looking_at(buf, &i, "--* (*:*): ") ||
2507                     /* Message notifications (same color as tells) */
2508                     looking_at(buf, &i, "* has left a message ") ||
2509                     looking_at(buf, &i, "* just sent you a message:\n") ||
2510                     /* Whispers and kibitzes */
2511                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512                     looking_at(buf, &i, "* kibitzes: ") ||
2513                     /* Channel tells */
2514                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2515
2516                   if (tkind == 1 && strchr(star_match[0], ':')) {
2517                       /* Avoid "tells you:" spoofs in channels */
2518                      tkind = 3;
2519                   }
2520                   if (star_match[0][0] == NULLCHAR ||
2521                       strchr(star_match[0], ' ') ||
2522                       (tkind == 3 && strchr(star_match[1], ' '))) {
2523                     /* Reject bogus matches */
2524                     i = oldi;
2525                   } else {
2526                     if (appData.colorize) {
2527                       if (oldi > next_out) {
2528                         SendToPlayer(&buf[next_out], oldi - next_out);
2529                         next_out = oldi;
2530                       }
2531                       switch (tkind) {
2532                       case 1:
2533                         Colorize(ColorTell, FALSE);
2534                         curColor = ColorTell;
2535                         break;
2536                       case 2:
2537                         Colorize(ColorKibitz, FALSE);
2538                         curColor = ColorKibitz;
2539                         break;
2540                       case 3:
2541                         p = strrchr(star_match[1], '(');
2542                         if (p == NULL) {
2543                           p = star_match[1];
2544                         } else {
2545                           p++;
2546                         }
2547                         if (atoi(p) == 1) {
2548                           Colorize(ColorChannel1, FALSE);
2549                           curColor = ColorChannel1;
2550                         } else {
2551                           Colorize(ColorChannel, FALSE);
2552                           curColor = ColorChannel;
2553                         }
2554                         break;
2555                       case 5:
2556                         curColor = ColorNormal;
2557                         break;
2558                       }
2559                     }
2560                     if (started == STARTED_NONE && appData.autoComment &&
2561                         (gameMode == IcsObserving ||
2562                          gameMode == IcsPlayingWhite ||
2563                          gameMode == IcsPlayingBlack)) {
2564                       parse_pos = i - oldi;
2565                       memcpy(parse, &buf[oldi], parse_pos);
2566                       parse[parse_pos] = NULLCHAR;
2567                       started = STARTED_COMMENT;
2568                       savingComment = TRUE;
2569                     } else {
2570                       started = STARTED_CHATTER;
2571                       savingComment = FALSE;
2572                     }
2573                     loggedOn = TRUE;
2574                     continue;
2575                   }
2576                 }
2577
2578                 if (looking_at(buf, &i, "* s-shouts: ") ||
2579                     looking_at(buf, &i, "* c-shouts: ")) {
2580                     if (appData.colorize) {
2581                         if (oldi > next_out) {
2582                             SendToPlayer(&buf[next_out], oldi - next_out);
2583                             next_out = oldi;
2584                         }
2585                         Colorize(ColorSShout, FALSE);
2586                         curColor = ColorSShout;
2587                     }
2588                     loggedOn = TRUE;
2589                     started = STARTED_CHATTER;
2590                     continue;
2591                 }
2592
2593                 if (looking_at(buf, &i, "--->")) {
2594                     loggedOn = TRUE;
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* shouts: ") ||
2599                     looking_at(buf, &i, "--> ")) {
2600                     if (appData.colorize) {
2601                         if (oldi > next_out) {
2602                             SendToPlayer(&buf[next_out], oldi - next_out);
2603                             next_out = oldi;
2604                         }
2605                         Colorize(ColorShout, FALSE);
2606                         curColor = ColorShout;
2607                     }
2608                     loggedOn = TRUE;
2609                     started = STARTED_CHATTER;
2610                     continue;
2611                 }
2612
2613                 if (looking_at( buf, &i, "Challenge:")) {
2614                     if (appData.colorize) {
2615                         if (oldi > next_out) {
2616                             SendToPlayer(&buf[next_out], oldi - next_out);
2617                             next_out = oldi;
2618                         }
2619                         Colorize(ColorChallenge, FALSE);
2620                         curColor = ColorChallenge;
2621                     }
2622                     loggedOn = TRUE;
2623                     continue;
2624                 }
2625
2626                 if (looking_at(buf, &i, "* offers you") ||
2627                     looking_at(buf, &i, "* offers to be") ||
2628                     looking_at(buf, &i, "* would like to") ||
2629                     looking_at(buf, &i, "* requests to") ||
2630                     looking_at(buf, &i, "Your opponent offers") ||
2631                     looking_at(buf, &i, "Your opponent requests")) {
2632
2633                     if (appData.colorize) {
2634                         if (oldi > next_out) {
2635                             SendToPlayer(&buf[next_out], oldi - next_out);
2636                             next_out = oldi;
2637                         }
2638                         Colorize(ColorRequest, FALSE);
2639                         curColor = ColorRequest;
2640                     }
2641                     continue;
2642                 }
2643
2644                 if (looking_at(buf, &i, "* (*) seeking")) {
2645                     if (appData.colorize) {
2646                         if (oldi > next_out) {
2647                             SendToPlayer(&buf[next_out], oldi - next_out);
2648                             next_out = oldi;
2649                         }
2650                         Colorize(ColorSeek, FALSE);
2651                         curColor = ColorSeek;
2652                     }
2653                     continue;
2654             }
2655
2656             if (looking_at(buf, &i, "\\   ")) {
2657                 if (prevColor != ColorNormal) {
2658                     if (oldi > next_out) {
2659                         SendToPlayer(&buf[next_out], oldi - next_out);
2660                         next_out = oldi;
2661                     }
2662                     Colorize(prevColor, TRUE);
2663                     curColor = prevColor;
2664                 }
2665                 if (savingComment) {
2666                     parse_pos = i - oldi;
2667                     memcpy(parse, &buf[oldi], parse_pos);
2668                     parse[parse_pos] = NULLCHAR;
2669                     started = STARTED_COMMENT;
2670                 } else {
2671                     started = STARTED_CHATTER;
2672                 }
2673                 continue;
2674             }
2675
2676             if (looking_at(buf, &i, "Black Strength :") ||
2677                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678                 looking_at(buf, &i, "<10>") ||
2679                 looking_at(buf, &i, "#@#")) {
2680                 /* Wrong board style */
2681                 loggedOn = TRUE;
2682                 SendToICS(ics_prefix);
2683                 SendToICS("set style 12\n");
2684                 SendToICS(ics_prefix);
2685                 SendToICS("refresh\n");
2686                 continue;
2687             }
2688             
2689             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2690                 ICSInitScript();
2691                 have_sent_ICS_logon = 1;
2692                 continue;
2693             }
2694               
2695             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2696                 (looking_at(buf, &i, "\n<12> ") ||
2697                  looking_at(buf, &i, "<12> "))) {
2698                 loggedOn = TRUE;
2699                 if (oldi > next_out) {
2700                     SendToPlayer(&buf[next_out], oldi - next_out);
2701                 }
2702                 next_out = i;
2703                 started = STARTED_BOARD;
2704                 parse_pos = 0;
2705                 continue;
2706             }
2707
2708             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709                 looking_at(buf, &i, "<b1> ")) {
2710                 if (oldi > next_out) {
2711                     SendToPlayer(&buf[next_out], oldi - next_out);
2712                 }
2713                 next_out = i;
2714                 started = STARTED_HOLDINGS;
2715                 parse_pos = 0;
2716                 continue;
2717             }
2718
2719             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2720                 loggedOn = TRUE;
2721                 /* Header for a move list -- first line */
2722
2723                 switch (ics_getting_history) {
2724                   case H_FALSE:
2725                     switch (gameMode) {
2726                       case IcsIdle:
2727                       case BeginningOfGame:
2728                         /* User typed "moves" or "oldmoves" while we
2729                            were idle.  Pretend we asked for these
2730                            moves and soak them up so user can step
2731                            through them and/or save them.
2732                            */
2733                         Reset(FALSE, TRUE);
2734                         gameMode = IcsObserving;
2735                         ModeHighlight();
2736                         ics_gamenum = -1;
2737                         ics_getting_history = H_GOT_UNREQ_HEADER;
2738                         break;
2739                       case EditGame: /*?*/
2740                       case EditPosition: /*?*/
2741                         /* Should above feature work in these modes too? */
2742                         /* For now it doesn't */
2743                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2744                         break;
2745                       default:
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                     }
2749                     break;
2750                   case H_REQUESTED:
2751                     /* Is this the right one? */
2752                     if (gameInfo.white && gameInfo.black &&
2753                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2754                         strcmp(gameInfo.black, star_match[2]) == 0) {
2755                         /* All is well */
2756                         ics_getting_history = H_GOT_REQ_HEADER;
2757                     }
2758                     break;
2759                   case H_GOT_REQ_HEADER:
2760                   case H_GOT_UNREQ_HEADER:
2761                   case H_GOT_UNWANTED_HEADER:
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: two headers"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                 }
2768
2769                 /* Save player ratings into gameInfo if needed */
2770                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772                     (gameInfo.whiteRating == -1 ||
2773                      gameInfo.blackRating == -1)) {
2774
2775                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2776                     gameInfo.blackRating = string_to_rating(star_match[3]);
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2779                               gameInfo.whiteRating, gameInfo.blackRating);
2780                 }
2781                 continue;
2782             }
2783
2784             if (looking_at(buf, &i,
2785               "* * match, initial time: * minute*, increment: * second")) {
2786                 /* Header for a move list -- second line */
2787                 /* Initial board will follow if this is a wild game */
2788                 if (gameInfo.event != NULL) free(gameInfo.event);
2789                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790                 gameInfo.event = StrSave(str);
2791                 /* [HGM] we switched variant. Translate boards if needed. */
2792                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2793                 continue;
2794             }
2795
2796             if (looking_at(buf, &i, "Move  ")) {
2797                 /* Beginning of a move list */
2798                 switch (ics_getting_history) {
2799                   case H_FALSE:
2800                     /* Normally should not happen */
2801                     /* Maybe user hit reset while we were parsing */
2802                     break;
2803                   case H_REQUESTED:
2804                     /* Happens if we are ignoring a move list that is not
2805                      * the one we just requested.  Common if the user
2806                      * tries to observe two games without turning off
2807                      * getMoveList */
2808                     break;
2809                   case H_GETTING_MOVES:
2810                     /* Should not happen */
2811                     DisplayError(_("Error gathering move list: nested"), 0);
2812                     ics_getting_history = H_FALSE;
2813                     break;
2814                   case H_GOT_REQ_HEADER:
2815                     ics_getting_history = H_GETTING_MOVES;
2816                     started = STARTED_MOVES;
2817                     parse_pos = 0;
2818                     if (oldi > next_out) {
2819                         SendToPlayer(&buf[next_out], oldi - next_out);
2820                     }
2821                     break;
2822                   case H_GOT_UNREQ_HEADER:
2823                     ics_getting_history = H_GETTING_MOVES;
2824                     started = STARTED_MOVES_NOHIDE;
2825                     parse_pos = 0;
2826                     break;
2827                   case H_GOT_UNWANTED_HEADER:
2828                     ics_getting_history = H_FALSE;
2829                     break;
2830                 }
2831                 continue;
2832             }                           
2833             
2834             if (looking_at(buf, &i, "% ") ||
2835                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837                 savingComment = FALSE;
2838                 switch (started) {
2839                   case STARTED_MOVES:
2840                   case STARTED_MOVES_NOHIDE:
2841                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842                     parse[parse_pos + i - oldi] = NULLCHAR;
2843                     ParseGameHistory(parse);
2844 #if ZIPPY
2845                     if (appData.zippyPlay && first.initDone) {
2846                         FeedMovesToProgram(&first, forwardMostMove);
2847                         if (gameMode == IcsPlayingWhite) {
2848                             if (WhiteOnMove(forwardMostMove)) {
2849                                 if (first.sendTime) {
2850                                   if (first.useColors) {
2851                                     SendToProgram("black\n", &first); 
2852                                   }
2853                                   SendTimeRemaining(&first, TRUE);
2854                                 }
2855                                 if (first.useColors) {
2856                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2857                                 }
2858                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859                                 first.maybeThinking = TRUE;
2860                             } else {
2861                                 if (first.usePlayother) {
2862                                   if (first.sendTime) {
2863                                     SendTimeRemaining(&first, TRUE);
2864                                   }
2865                                   SendToProgram("playother\n", &first);
2866                                   firstMove = FALSE;
2867                                 } else {
2868                                   firstMove = TRUE;
2869                                 }
2870                             }
2871                         } else if (gameMode == IcsPlayingBlack) {
2872                             if (!WhiteOnMove(forwardMostMove)) {
2873                                 if (first.sendTime) {
2874                                   if (first.useColors) {
2875                                     SendToProgram("white\n", &first);
2876                                   }
2877                                   SendTimeRemaining(&first, FALSE);
2878                                 }
2879                                 if (first.useColors) {
2880                                   SendToProgram("black\n", &first);
2881                                 }
2882                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883                                 first.maybeThinking = TRUE;
2884                             } else {
2885                                 if (first.usePlayother) {
2886                                   if (first.sendTime) {
2887                                     SendTimeRemaining(&first, FALSE);
2888                                   }
2889                                   SendToProgram("playother\n", &first);
2890                                   firstMove = FALSE;
2891                                 } else {
2892                                   firstMove = TRUE;
2893                                 }
2894                             }
2895                         }                       
2896                     }
2897 #endif
2898                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2899                         /* Moves came from oldmoves or moves command
2900                            while we weren't doing anything else.
2901                            */
2902                         currentMove = forwardMostMove;
2903                         ClearHighlights();/*!!could figure this out*/
2904                         flipView = appData.flipView;
2905                         DrawPosition(FALSE, boards[currentMove]);
2906                         DisplayBothClocks();
2907                         sprintf(str, "%s vs. %s",
2908                                 gameInfo.white, gameInfo.black);
2909                         DisplayTitle(str);
2910                         gameMode = IcsIdle;
2911                     } else {
2912                         /* Moves were history of an active game */
2913                         if (gameInfo.resultDetails != NULL) {
2914                             free(gameInfo.resultDetails);
2915                             gameInfo.resultDetails = NULL;
2916                         }
2917                     }
2918                     HistorySet(parseList, backwardMostMove,
2919                                forwardMostMove, currentMove-1);
2920                     DisplayMove(currentMove - 1);
2921                     if (started == STARTED_MOVES) next_out = i;
2922                     started = STARTED_NONE;
2923                     ics_getting_history = H_FALSE;
2924                     break;
2925
2926                   case STARTED_OBSERVE:
2927                     started = STARTED_NONE;
2928                     SendToICS(ics_prefix);
2929                     SendToICS("refresh\n");
2930                     break;
2931
2932                   default:
2933                     break;
2934                 }
2935                 if(bookHit) { // [HGM] book: simulate book reply
2936                     static char bookMove[MSG_SIZ]; // a bit generous?
2937
2938                     programStats.nodes = programStats.depth = programStats.time = 
2939                     programStats.score = programStats.got_only_move = 0;
2940                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2941
2942                     strcpy(bookMove, "move ");
2943                     strcat(bookMove, bookHit);
2944                     HandleMachineMove(bookMove, &first);
2945                 }
2946                 continue;
2947             }
2948             
2949             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950                  started == STARTED_HOLDINGS ||
2951                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952                 /* Accumulate characters in move list or board */
2953                 parse[parse_pos++] = buf[i];
2954             }
2955             
2956             /* Start of game messages.  Mostly we detect start of game
2957                when the first board image arrives.  On some versions
2958                of the ICS, though, we need to do a "refresh" after starting
2959                to observe in order to get the current board right away. */
2960             if (looking_at(buf, &i, "Adding game * to observation list")) {
2961                 started = STARTED_OBSERVE;
2962                 continue;
2963             }
2964
2965             /* Handle auto-observe */
2966             if (appData.autoObserve &&
2967                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2969                 char *player;
2970                 /* Choose the player that was highlighted, if any. */
2971                 if (star_match[0][0] == '\033' ||
2972                     star_match[1][0] != '\033') {
2973                     player = star_match[0];
2974                 } else {
2975                     player = star_match[2];
2976                 }
2977                 sprintf(str, "%sobserve %s\n",
2978                         ics_prefix, StripHighlightAndTitle(player));
2979                 SendToICS(str);
2980
2981                 /* Save ratings from notify string */
2982                 strcpy(player1Name, star_match[0]);
2983                 player1Rating = string_to_rating(star_match[1]);
2984                 strcpy(player2Name, star_match[2]);
2985                 player2Rating = string_to_rating(star_match[3]);
2986
2987                 if (appData.debugMode)
2988                   fprintf(debugFP, 
2989                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2990                           player1Name, player1Rating,
2991                           player2Name, player2Rating);
2992
2993                 continue;
2994             }
2995
2996             /* Deal with automatic examine mode after a game,
2997                and with IcsObserving -> IcsExamining transition */
2998             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999                 looking_at(buf, &i, "has made you an examiner of game *")) {
3000
3001                 int gamenum = atoi(star_match[0]);
3002                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003                     gamenum == ics_gamenum) {
3004                     /* We were already playing or observing this game;
3005                        no need to refetch history */
3006                     gameMode = IcsExamining;
3007                     if (pausing) {
3008                         pauseExamForwardMostMove = forwardMostMove;
3009                     } else if (currentMove < forwardMostMove) {
3010                         ForwardInner(forwardMostMove);
3011                     }
3012                 } else {
3013                     /* I don't think this case really can happen */
3014                     SendToICS(ics_prefix);
3015                     SendToICS("refresh\n");
3016                 }
3017                 continue;
3018             }    
3019             
3020             /* Error messages */
3021 //          if (ics_user_moved) {
3022             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023                 if (looking_at(buf, &i, "Illegal move") ||
3024                     looking_at(buf, &i, "Not a legal move") ||
3025                     looking_at(buf, &i, "Your king is in check") ||
3026                     looking_at(buf, &i, "It isn't your turn") ||
3027                     looking_at(buf, &i, "It is not your move")) {
3028                     /* Illegal move */
3029                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030                         currentMove = --forwardMostMove;
3031                         DisplayMove(currentMove - 1); /* before DMError */
3032                         DrawPosition(FALSE, boards[currentMove]);
3033                         SwitchClocks();
3034                         DisplayBothClocks();
3035                     }
3036                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037                     ics_user_moved = 0;
3038                     continue;
3039                 }
3040             }
3041
3042             if (looking_at(buf, &i, "still have time") ||
3043                 looking_at(buf, &i, "not out of time") ||
3044                 looking_at(buf, &i, "either player is out of time") ||
3045                 looking_at(buf, &i, "has timeseal; checking")) {
3046                 /* We must have called his flag a little too soon */
3047                 whiteFlag = blackFlag = FALSE;
3048                 continue;
3049             }
3050
3051             if (looking_at(buf, &i, "added * seconds to") ||
3052                 looking_at(buf, &i, "seconds were added to")) {
3053                 /* Update the clocks */
3054                 SendToICS(ics_prefix);
3055                 SendToICS("refresh\n");
3056                 continue;
3057             }
3058
3059             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060                 ics_clock_paused = TRUE;
3061                 StopClocks();
3062                 continue;
3063             }
3064
3065             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066                 ics_clock_paused = FALSE;
3067                 StartClocks();
3068                 continue;
3069             }
3070
3071             /* Grab player ratings from the Creating: message.
3072                Note we have to check for the special case when
3073                the ICS inserts things like [white] or [black]. */
3074             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3076                 /* star_matches:
3077                    0    player 1 name (not necessarily white)
3078                    1    player 1 rating
3079                    2    empty, white, or black (IGNORED)
3080                    3    player 2 name (not necessarily black)
3081                    4    player 2 rating
3082                    
3083                    The names/ratings are sorted out when the game
3084                    actually starts (below).
3085                 */
3086                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087                 player1Rating = string_to_rating(star_match[1]);
3088                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089                 player2Rating = string_to_rating(star_match[4]);
3090
3091                 if (appData.debugMode)
3092                   fprintf(debugFP, 
3093                           "Ratings from 'Creating:' %s %d, %s %d\n",
3094                           player1Name, player1Rating,
3095                           player2Name, player2Rating);
3096
3097                 continue;
3098             }
3099             
3100             /* Improved generic start/end-of-game messages */
3101             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103                 /* If tkind == 0: */
3104                 /* star_match[0] is the game number */
3105                 /*           [1] is the white player's name */
3106                 /*           [2] is the black player's name */
3107                 /* For end-of-game: */
3108                 /*           [3] is the reason for the game end */
3109                 /*           [4] is a PGN end game-token, preceded by " " */
3110                 /* For start-of-game: */
3111                 /*           [3] begins with "Creating" or "Continuing" */
3112                 /*           [4] is " *" or empty (don't care). */
3113                 int gamenum = atoi(star_match[0]);
3114                 char *whitename, *blackname, *why, *endtoken;
3115                 ChessMove endtype = (ChessMove) 0;
3116
3117                 if (tkind == 0) {
3118                   whitename = star_match[1];
3119                   blackname = star_match[2];
3120                   why = star_match[3];
3121                   endtoken = star_match[4];
3122                 } else {
3123                   whitename = star_match[1];
3124                   blackname = star_match[3];
3125                   why = star_match[5];
3126                   endtoken = star_match[6];
3127                 }
3128
3129                 /* Game start messages */
3130                 if (strncmp(why, "Creating ", 9) == 0 ||
3131                     strncmp(why, "Continuing ", 11) == 0) {
3132                     gs_gamenum = gamenum;
3133                     strcpy(gs_kind, strchr(why, ' ') + 1);
3134 #if ZIPPY
3135                     if (appData.zippyPlay) {
3136                         ZippyGameStart(whitename, blackname);
3137                     }
3138 #endif /*ZIPPY*/
3139                     continue;
3140                 }
3141
3142                 /* Game end messages */
3143                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144                     ics_gamenum != gamenum) {
3145                     continue;
3146                 }
3147                 while (endtoken[0] == ' ') endtoken++;
3148                 switch (endtoken[0]) {
3149                   case '*':
3150                   default:
3151                     endtype = GameUnfinished;
3152                     break;
3153                   case '0':
3154                     endtype = BlackWins;
3155                     break;
3156                   case '1':
3157                     if (endtoken[1] == '/')
3158                       endtype = GameIsDrawn;
3159                     else
3160                       endtype = WhiteWins;
3161                     break;
3162                 }
3163                 GameEnds(endtype, why, GE_ICS);
3164 #if ZIPPY
3165                 if (appData.zippyPlay && first.initDone) {
3166                     ZippyGameEnd(endtype, why);
3167                     if (first.pr == NULL) {
3168                       /* Start the next process early so that we'll
3169                          be ready for the next challenge */
3170                       StartChessProgram(&first);
3171                     }
3172                     /* Send "new" early, in case this command takes
3173                        a long time to finish, so that we'll be ready
3174                        for the next challenge. */
3175                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3176                     Reset(TRUE, TRUE);
3177                 }
3178 #endif /*ZIPPY*/
3179                 continue;
3180             }
3181
3182             if (looking_at(buf, &i, "Removing game * from observation") ||
3183                 looking_at(buf, &i, "no longer observing game *") ||
3184                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185                 if (gameMode == IcsObserving &&
3186                     atoi(star_match[0]) == ics_gamenum)
3187                   {
3188                       /* icsEngineAnalyze */
3189                       if (appData.icsEngineAnalyze) {
3190                             ExitAnalyzeMode();
3191                             ModeHighlight();
3192                       }
3193                       StopClocks();
3194                       gameMode = IcsIdle;
3195                       ics_gamenum = -1;
3196                       ics_user_moved = FALSE;
3197                   }
3198                 continue;
3199             }
3200
3201             if (looking_at(buf, &i, "no longer examining game *")) {
3202                 if (gameMode == IcsExamining &&
3203                     atoi(star_match[0]) == ics_gamenum)
3204                   {
3205                       gameMode = IcsIdle;
3206                       ics_gamenum = -1;
3207                       ics_user_moved = FALSE;
3208                   }
3209                 continue;
3210             }
3211
3212             /* Advance leftover_start past any newlines we find,
3213                so only partial lines can get reparsed */
3214             if (looking_at(buf, &i, "\n")) {
3215                 prevColor = curColor;
3216                 if (curColor != ColorNormal) {
3217                     if (oldi > next_out) {
3218                         SendToPlayer(&buf[next_out], oldi - next_out);
3219                         next_out = oldi;
3220                     }
3221                     Colorize(ColorNormal, FALSE);
3222                     curColor = ColorNormal;
3223                 }
3224                 if (started == STARTED_BOARD) {
3225                     started = STARTED_NONE;
3226                     parse[parse_pos] = NULLCHAR;
3227                     ParseBoard12(parse);
3228                     ics_user_moved = 0;
3229
3230                     /* Send premove here */
3231                     if (appData.premove) {
3232                       char str[MSG_SIZ];
3233                       if (currentMove == 0 &&
3234                           gameMode == IcsPlayingWhite &&
3235                           appData.premoveWhite) {
3236                         sprintf(str, "%s%s\n", ics_prefix,
3237                                 appData.premoveWhiteText);
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                         SendToICS(str);
3241                       } else if (currentMove == 1 &&
3242                                  gameMode == IcsPlayingBlack &&
3243                                  appData.premoveBlack) {
3244                         sprintf(str, "%s%s\n", ics_prefix,
3245                                 appData.premoveBlackText);
3246                         if (appData.debugMode)
3247                           fprintf(debugFP, "Sending premove:\n");
3248                         SendToICS(str);
3249                       } else if (gotPremove) {
3250                         gotPremove = 0;
3251                         ClearPremoveHighlights();
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                           UserMoveEvent(premoveFromX, premoveFromY, 
3255                                         premoveToX, premoveToY, 
3256                                         premovePromoChar);
3257                       }
3258                     }
3259
3260                     /* Usually suppress following prompt */
3261                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262                         if (looking_at(buf, &i, "*% ")) {
3263                             savingComment = FALSE;
3264                         }
3265                     }
3266                     next_out = i;
3267                 } else if (started == STARTED_HOLDINGS) {
3268                     int gamenum;
3269                     char new_piece[MSG_SIZ];
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     if (appData.debugMode)
3273                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274                                                         parse, currentMove);
3275                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276                         gamenum == ics_gamenum) {
3277                         if (gameInfo.variant == VariantNormal) {
3278                           /* [HGM] We seem to switch variant during a game!
3279                            * Presumably no holdings were displayed, so we have
3280                            * to move the position two files to the right to
3281                            * create room for them!
3282                            */
3283                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3284                           /* Get a move list just to see the header, which
3285                              will tell us whether this is really bug or zh */
3286                           if (ics_getting_history == H_FALSE) {
3287                             ics_getting_history = H_REQUESTED;
3288                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3289                             SendToICS(str);
3290                           }
3291                         }
3292                         new_piece[0] = NULLCHAR;
3293                         sscanf(parse, "game %d white [%s black [%s <- %s",
3294                                &gamenum, white_holding, black_holding,
3295                                new_piece);
3296                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3297                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3298                         /* [HGM] copy holdings to board holdings area */
3299                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3300                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3301 #if ZIPPY
3302                         if (appData.zippyPlay && first.initDone) {
3303                             ZippyHoldings(white_holding, black_holding,
3304                                           new_piece);
3305                         }
3306 #endif /*ZIPPY*/
3307                         if (tinyLayout || smallLayout) {
3308                             char wh[16], bh[16];
3309                             PackHolding(wh, white_holding);
3310                             PackHolding(bh, black_holding);
3311                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3312                                     gameInfo.white, gameInfo.black);
3313                         } else {
3314                             sprintf(str, "%s [%s] vs. %s [%s]",
3315                                     gameInfo.white, white_holding,
3316                                     gameInfo.black, black_holding);
3317                         }
3318
3319                         DrawPosition(FALSE, boards[currentMove]);
3320                         DisplayTitle(str);
3321                     }
3322                     /* Suppress following prompt */
3323                     if (looking_at(buf, &i, "*% ")) {
3324                         savingComment = FALSE;
3325                     }
3326                     next_out = i;
3327                 }
3328                 continue;
3329             }
3330
3331             i++;                /* skip unparsed character and loop back */
3332         }
3333         
3334         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3335             started != STARTED_HOLDINGS && i > next_out) {
3336             SendToPlayer(&buf[next_out], i - next_out);
3337             next_out = i;
3338         }
3339         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3340         
3341         leftover_len = buf_len - leftover_start;
3342         /* if buffer ends with something we couldn't parse,
3343            reparse it after appending the next read */
3344         
3345     } else if (count == 0) {
3346         RemoveInputSource(isr);
3347         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3348     } else {
3349         DisplayFatalError(_("Error reading from ICS"), error, 1);
3350     }
3351 }
3352
3353
3354 /* Board style 12 looks like this:
3355    
3356    <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
3357    
3358  * The "<12> " is stripped before it gets to this routine.  The two
3359  * trailing 0's (flip state and clock ticking) are later addition, and
3360  * some chess servers may not have them, or may have only the first.
3361  * Additional trailing fields may be added in the future.  
3362  */
3363
3364 #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"
3365
3366 #define RELATION_OBSERVING_PLAYED    0
3367 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3368 #define RELATION_PLAYING_MYMOVE      1
3369 #define RELATION_PLAYING_NOTMYMOVE  -1
3370 #define RELATION_EXAMINING           2
3371 #define RELATION_ISOLATED_BOARD     -3
3372 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3373
3374 void
3375 ParseBoard12(string)
3376      char *string;
3377
3378     GameMode newGameMode;
3379     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3380     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3381     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3382     char to_play, board_chars[200];
3383     char move_str[500], str[500], elapsed_time[500];
3384     char black[32], white[32];
3385     Board board;
3386     int prevMove = currentMove;
3387     int ticking = 2;
3388     ChessMove moveType;
3389     int fromX, fromY, toX, toY;
3390     char promoChar;
3391     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3392     char *bookHit = NULL; // [HGM] book
3393
3394     fromX = fromY = toX = toY = -1;
3395     
3396     newGame = FALSE;
3397
3398     if (appData.debugMode)
3399       fprintf(debugFP, _("Parsing board: %s\n"), string);
3400
3401     move_str[0] = NULLCHAR;
3402     elapsed_time[0] = NULLCHAR;
3403     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3404         int  i = 0, j;
3405         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3406             if(string[i] == ' ') { ranks++; files = 0; }
3407             else files++;
3408             i++;
3409         }
3410         for(j = 0; j <i; j++) board_chars[j] = string[j];
3411         board_chars[i] = '\0';
3412         string += i + 1;
3413     }
3414     n = sscanf(string, PATTERN, &to_play, &double_push,
3415                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3416                &gamenum, white, black, &relation, &basetime, &increment,
3417                &white_stren, &black_stren, &white_time, &black_time,
3418                &moveNum, str, elapsed_time, move_str, &ics_flip,
3419                &ticking);
3420
3421     if (n < 21) {
3422         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3423         DisplayError(str, 0);
3424         return;
3425     }
3426
3427     /* Convert the move number to internal form */
3428     moveNum = (moveNum - 1) * 2;
3429     if (to_play == 'B') moveNum++;
3430     if (moveNum >= MAX_MOVES) {
3431       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3432                         0, 1);
3433       return;
3434     }
3435     
3436     switch (relation) {
3437       case RELATION_OBSERVING_PLAYED:
3438       case RELATION_OBSERVING_STATIC:
3439         if (gamenum == -1) {
3440             /* Old ICC buglet */
3441             relation = RELATION_OBSERVING_STATIC;
3442         }
3443         newGameMode = IcsObserving;
3444         break;
3445       case RELATION_PLAYING_MYMOVE:
3446       case RELATION_PLAYING_NOTMYMOVE:
3447         newGameMode =
3448           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3449             IcsPlayingWhite : IcsPlayingBlack;
3450         break;
3451       case RELATION_EXAMINING:
3452         newGameMode = IcsExamining;
3453         break;
3454       case RELATION_ISOLATED_BOARD:
3455       default:
3456         /* Just display this board.  If user was doing something else,
3457            we will forget about it until the next board comes. */ 
3458         newGameMode = IcsIdle;
3459         break;
3460       case RELATION_STARTING_POSITION:
3461         newGameMode = gameMode;
3462         break;
3463     }
3464     
3465     /* Modify behavior for initial board display on move listing
3466        of wild games.
3467        */
3468     switch (ics_getting_history) {
3469       case H_FALSE:
3470       case H_REQUESTED:
3471         break;
3472       case H_GOT_REQ_HEADER:
3473       case H_GOT_UNREQ_HEADER:
3474         /* This is the initial position of the current game */
3475         gamenum = ics_gamenum;
3476         moveNum = 0;            /* old ICS bug workaround */
3477         if (to_play == 'B') {
3478           startedFromSetupPosition = TRUE;
3479           blackPlaysFirst = TRUE;
3480           moveNum = 1;
3481           if (forwardMostMove == 0) forwardMostMove = 1;
3482           if (backwardMostMove == 0) backwardMostMove = 1;
3483           if (currentMove == 0) currentMove = 1;
3484         }
3485         newGameMode = gameMode;
3486         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3487         break;
3488       case H_GOT_UNWANTED_HEADER:
3489         /* This is an initial board that we don't want */
3490         return;
3491       case H_GETTING_MOVES:
3492         /* Should not happen */
3493         DisplayError(_("Error gathering move list: extra board"), 0);
3494         ics_getting_history = H_FALSE;
3495         return;
3496     }
3497     
3498     /* Take action if this is the first board of a new game, or of a
3499        different game than is currently being displayed.  */
3500     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3501         relation == RELATION_ISOLATED_BOARD) {
3502         
3503         /* Forget the old game and get the history (if any) of the new one */
3504         if (gameMode != BeginningOfGame) {
3505           Reset(FALSE, TRUE);
3506         }
3507         newGame = TRUE;
3508         if (appData.autoRaiseBoard) BoardToTop();
3509         prevMove = -3;
3510         if (gamenum == -1) {
3511             newGameMode = IcsIdle;
3512         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3513                    appData.getMoveList) {
3514             /* Need to get game history */
3515             ics_getting_history = H_REQUESTED;
3516             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3517             SendToICS(str);
3518         }
3519         
3520         /* Initially flip the board to have black on the bottom if playing
3521            black or if the ICS flip flag is set, but let the user change
3522            it with the Flip View button. */
3523         flipView = appData.autoFlipView ? 
3524           (newGameMode == IcsPlayingBlack) || ics_flip :
3525           appData.flipView;
3526         
3527         /* Done with values from previous mode; copy in new ones */
3528         gameMode = newGameMode;
3529         ModeHighlight();
3530         ics_gamenum = gamenum;
3531         if (gamenum == gs_gamenum) {
3532             int klen = strlen(gs_kind);
3533             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3534             sprintf(str, "ICS %s", gs_kind);
3535             gameInfo.event = StrSave(str);
3536         } else {
3537             gameInfo.event = StrSave("ICS game");
3538         }
3539         gameInfo.site = StrSave(appData.icsHost);
3540         gameInfo.date = PGNDate();
3541         gameInfo.round = StrSave("-");
3542         gameInfo.white = StrSave(white);
3543         gameInfo.black = StrSave(black);
3544         timeControl = basetime * 60 * 1000;
3545         timeControl_2 = 0;
3546         timeIncrement = increment * 1000;
3547         movesPerSession = 0;
3548         gameInfo.timeControl = TimeControlTagValue();
3549         VariantSwitch(board, StringToVariant(gameInfo.event) );
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3552     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3553     setbuf(debugFP, NULL);
3554   }
3555
3556         gameInfo.outOfBook = NULL;
3557         
3558         /* Do we have the ratings? */
3559         if (strcmp(player1Name, white) == 0 &&
3560             strcmp(player2Name, black) == 0) {
3561             if (appData.debugMode)
3562               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3563                       player1Rating, player2Rating);
3564             gameInfo.whiteRating = player1Rating;
3565             gameInfo.blackRating = player2Rating;
3566         } else if (strcmp(player2Name, white) == 0 &&
3567                    strcmp(player1Name, black) == 0) {
3568             if (appData.debugMode)
3569               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3570                       player2Rating, player1Rating);
3571             gameInfo.whiteRating = player2Rating;
3572             gameInfo.blackRating = player1Rating;
3573         }
3574         player1Name[0] = player2Name[0] = NULLCHAR;
3575
3576         /* Silence shouts if requested */
3577         if (appData.quietPlay &&
3578             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3579             SendToICS(ics_prefix);
3580             SendToICS("set shout 0\n");
3581         }
3582     }
3583     
3584     /* Deal with midgame name changes */
3585     if (!newGame) {
3586         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3587             if (gameInfo.white) free(gameInfo.white);
3588             gameInfo.white = StrSave(white);
3589         }
3590         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3591             if (gameInfo.black) free(gameInfo.black);
3592             gameInfo.black = StrSave(black);
3593         }
3594     }
3595     
3596     /* Throw away game result if anything actually changes in examine mode */
3597     if (gameMode == IcsExamining && !newGame) {
3598         gameInfo.result = GameUnfinished;
3599         if (gameInfo.resultDetails != NULL) {
3600             free(gameInfo.resultDetails);
3601             gameInfo.resultDetails = NULL;
3602         }
3603     }
3604     
3605     /* In pausing && IcsExamining mode, we ignore boards coming
3606        in if they are in a different variation than we are. */
3607     if (pauseExamInvalid) return;
3608     if (pausing && gameMode == IcsExamining) {
3609         if (moveNum <= pauseExamForwardMostMove) {
3610             pauseExamInvalid = TRUE;
3611             forwardMostMove = pauseExamForwardMostMove;
3612             return;
3613         }
3614     }
3615     
3616   if (appData.debugMode) {
3617     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3618   }
3619     /* Parse the board */
3620     for (k = 0; k < ranks; k++) {
3621       for (j = 0; j < files; j++)
3622         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3623       if(gameInfo.holdingsWidth > 1) {
3624            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3625            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3626       }
3627     }
3628     CopyBoard(boards[moveNum], board);
3629     if (moveNum == 0) {
3630         startedFromSetupPosition =
3631           !CompareBoards(board, initialPosition);
3632         if(startedFromSetupPosition)
3633             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3634     }
3635
3636     /* [HGM] Set castling rights. Take the outermost Rooks,
3637        to make it also work for FRC opening positions. Note that board12
3638        is really defective for later FRC positions, as it has no way to
3639        indicate which Rook can castle if they are on the same side of King.
3640        For the initial position we grant rights to the outermost Rooks,
3641        and remember thos rights, and we then copy them on positions
3642        later in an FRC game. This means WB might not recognize castlings with
3643        Rooks that have moved back to their original position as illegal,
3644        but in ICS mode that is not its job anyway.
3645     */
3646     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3647     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3648
3649         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650             if(board[0][i] == WhiteRook) j = i;
3651         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653             if(board[0][i] == WhiteRook) j = i;
3654         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3655         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3656             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3657         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3658         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3659             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3660         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3661
3662         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3663         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3664             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3665         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3666             if(board[BOARD_HEIGHT-1][k] == bKing)
3667                 initialRights[5] = castlingRights[moveNum][5] = k;
3668     } else { int r;
3669         r = castlingRights[moveNum][0] = initialRights[0];
3670         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3671         r = castlingRights[moveNum][1] = initialRights[1];
3672         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3673         r = castlingRights[moveNum][3] = initialRights[3];
3674         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3675         r = castlingRights[moveNum][4] = initialRights[4];
3676         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3677         /* wildcastle kludge: always assume King has rights */
3678         r = castlingRights[moveNum][2] = initialRights[2];
3679         r = castlingRights[moveNum][5] = initialRights[5];
3680     }
3681     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3682     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3683
3684     
3685     if (ics_getting_history == H_GOT_REQ_HEADER ||
3686         ics_getting_history == H_GOT_UNREQ_HEADER) {
3687         /* This was an initial position from a move list, not
3688            the current position */
3689         return;
3690     }
3691     
3692     /* Update currentMove and known move number limits */
3693     newMove = newGame || moveNum > forwardMostMove;
3694
3695     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3696     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3697         takeback = forwardMostMove - moveNum;
3698         for (i = 0; i < takeback; i++) {
3699              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3700              SendToProgram("undo\n", &first);
3701         }
3702     }
3703
3704     if (newGame) {
3705         forwardMostMove = backwardMostMove = currentMove = moveNum;
3706         if (gameMode == IcsExamining && moveNum == 0) {
3707           /* Workaround for ICS limitation: we are not told the wild
3708              type when starting to examine a game.  But if we ask for
3709              the move list, the move list header will tell us */
3710             ics_getting_history = H_REQUESTED;
3711             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3712             SendToICS(str);
3713         }
3714     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3715                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3716         forwardMostMove = moveNum;
3717         if (!pausing || currentMove > forwardMostMove)
3718           currentMove = forwardMostMove;
3719     } else {
3720         /* New part of history that is not contiguous with old part */ 
3721         if (pausing && gameMode == IcsExamining) {
3722             pauseExamInvalid = TRUE;
3723             forwardMostMove = pauseExamForwardMostMove;
3724             return;
3725         }
3726         forwardMostMove = backwardMostMove = currentMove = moveNum;
3727         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3728             ics_getting_history = H_REQUESTED;
3729             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3730             SendToICS(str);
3731         }
3732     }
3733     
3734     /* Update the clocks */
3735     if (strchr(elapsed_time, '.')) {
3736       /* Time is in ms */
3737       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3738       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3739     } else {
3740       /* Time is in seconds */
3741       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3742       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3743     }
3744       
3745
3746 #if ZIPPY
3747     if (appData.zippyPlay && newGame &&
3748         gameMode != IcsObserving && gameMode != IcsIdle &&
3749         gameMode != IcsExamining)
3750       ZippyFirstBoard(moveNum, basetime, increment);
3751 #endif
3752     
3753     /* Put the move on the move list, first converting
3754        to canonical algebraic form. */
3755     if (moveNum > 0) {
3756   if (appData.debugMode) {
3757     if (appData.debugMode) { int f = forwardMostMove;
3758         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3759                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3760     }
3761     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3762     fprintf(debugFP, "moveNum = %d\n", moveNum);
3763     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3764     setbuf(debugFP, NULL);
3765   }
3766         if (moveNum <= backwardMostMove) {
3767             /* We don't know what the board looked like before
3768                this move.  Punt. */
3769             strcpy(parseList[moveNum - 1], move_str);
3770             strcat(parseList[moveNum - 1], " ");
3771             strcat(parseList[moveNum - 1], elapsed_time);
3772             moveList[moveNum - 1][0] = NULLCHAR;
3773         } else if (strcmp(move_str, "none") == 0) {
3774             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3775             /* Again, we don't know what the board looked like;
3776                this is really the start of the game. */
3777             parseList[moveNum - 1][0] = NULLCHAR;
3778             moveList[moveNum - 1][0] = NULLCHAR;
3779             backwardMostMove = moveNum;
3780             startedFromSetupPosition = TRUE;
3781             fromX = fromY = toX = toY = -1;
3782         } else {
3783           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3784           //                 So we parse the long-algebraic move string in stead of the SAN move
3785           int valid; char buf[MSG_SIZ], *prom;
3786
3787           // str looks something like "Q/a1-a2"; kill the slash
3788           if(str[1] == '/') 
3789                 sprintf(buf, "%c%s", str[0], str+2);
3790           else  strcpy(buf, str); // might be castling
3791           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3792                 strcat(buf, prom); // long move lacks promo specification!
3793           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3794                 if(appData.debugMode) 
3795                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3796                 strcpy(move_str, buf);
3797           }
3798           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3799                                 &fromX, &fromY, &toX, &toY, &promoChar)
3800                || ParseOneMove(buf, moveNum - 1, &moveType,
3801                                 &fromX, &fromY, &toX, &toY, &promoChar);
3802           // end of long SAN patch
3803           if (valid) {
3804             (void) CoordsToAlgebraic(boards[moveNum - 1],
3805                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3806                                      fromY, fromX, toY, toX, promoChar,
3807                                      parseList[moveNum-1]);
3808             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3809                              castlingRights[moveNum]) ) {
3810               case MT_NONE:
3811               case MT_STALEMATE:
3812               default:
3813                 break;
3814               case MT_CHECK:
3815                 if(gameInfo.variant != VariantShogi)
3816                     strcat(parseList[moveNum - 1], "+");
3817                 break;
3818               case MT_CHECKMATE:
3819               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3820                 strcat(parseList[moveNum - 1], "#");
3821                 break;
3822             }
3823             strcat(parseList[moveNum - 1], " ");
3824             strcat(parseList[moveNum - 1], elapsed_time);
3825             /* currentMoveString is set as a side-effect of ParseOneMove */
3826             strcpy(moveList[moveNum - 1], currentMoveString);
3827             strcat(moveList[moveNum - 1], "\n");
3828           } else {
3829             /* Move from ICS was illegal!?  Punt. */
3830   if (appData.debugMode) {
3831     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3832     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3833   }
3834             strcpy(parseList[moveNum - 1], move_str);
3835             strcat(parseList[moveNum - 1], " ");
3836             strcat(parseList[moveNum - 1], elapsed_time);
3837             moveList[moveNum - 1][0] = NULLCHAR;
3838             fromX = fromY = toX = toY = -1;
3839           }
3840         }
3841   if (appData.debugMode) {
3842     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3843     setbuf(debugFP, NULL);
3844   }
3845
3846 #if ZIPPY
3847         /* Send move to chess program (BEFORE animating it). */
3848         if (appData.zippyPlay && !newGame && newMove && 
3849            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3850
3851             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3852                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3853                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3854                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3855                             move_str);
3856                     DisplayError(str, 0);
3857                 } else {
3858                     if (first.sendTime) {
3859                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3860                     }
3861                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3862                     if (firstMove && !bookHit) {
3863                         firstMove = FALSE;
3864                         if (first.useColors) {
3865                           SendToProgram(gameMode == IcsPlayingWhite ?
3866                                         "white\ngo\n" :
3867                                         "black\ngo\n", &first);
3868                         } else {
3869                           SendToProgram("go\n", &first);
3870                         }
3871                         first.maybeThinking = TRUE;
3872                     }
3873                 }
3874             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3875               if (moveList[moveNum - 1][0] == NULLCHAR) {
3876                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3877                 DisplayError(str, 0);
3878               } else {
3879                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3880                 SendMoveToProgram(moveNum - 1, &first);
3881               }
3882             }
3883         }
3884 #endif
3885     }
3886
3887     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3888         /* If move comes from a remote source, animate it.  If it
3889            isn't remote, it will have already been animated. */
3890         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3891             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3892         }
3893         if (!pausing && appData.highlightLastMove) {
3894             SetHighlights(fromX, fromY, toX, toY);
3895         }
3896     }
3897     
3898     /* Start the clocks */
3899     whiteFlag = blackFlag = FALSE;
3900     appData.clockMode = !(basetime == 0 && increment == 0);
3901     if (ticking == 0) {
3902       ics_clock_paused = TRUE;
3903       StopClocks();
3904     } else if (ticking == 1) {
3905       ics_clock_paused = FALSE;
3906     }
3907     if (gameMode == IcsIdle ||
3908         relation == RELATION_OBSERVING_STATIC ||
3909         relation == RELATION_EXAMINING ||
3910         ics_clock_paused)
3911       DisplayBothClocks();
3912     else
3913       StartClocks();
3914     
3915     /* Display opponents and material strengths */
3916     if (gameInfo.variant != VariantBughouse &&
3917         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3918         if (tinyLayout || smallLayout) {
3919             if(gameInfo.variant == VariantNormal)
3920                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3921                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3922                     basetime, increment);
3923             else
3924                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3925                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3926                     basetime, increment, (int) gameInfo.variant);
3927         } else {
3928             if(gameInfo.variant == VariantNormal)
3929                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3930                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3931                     basetime, increment);
3932             else
3933                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3934                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3935                     basetime, increment, VariantName(gameInfo.variant));
3936         }
3937         DisplayTitle(str);
3938   if (appData.debugMode) {
3939     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3940   }
3941     }
3942
3943    
3944     /* Display the board */
3945     if (!pausing && !appData.noGUI) {
3946       
3947       if (appData.premove)
3948           if (!gotPremove || 
3949              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951               ClearPremoveHighlights();
3952
3953       DrawPosition(FALSE, boards[currentMove]);
3954       DisplayMove(moveNum - 1);
3955       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3958         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3959       }
3960     }
3961
3962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3963 #if ZIPPY
3964     if(bookHit) { // [HGM] book: simulate book reply
3965         static char bookMove[MSG_SIZ]; // a bit generous?
3966
3967         programStats.nodes = programStats.depth = programStats.time = 
3968         programStats.score = programStats.got_only_move = 0;
3969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3970
3971         strcpy(bookMove, "move ");
3972         strcat(bookMove, bookHit);
3973         HandleMachineMove(bookMove, &first);
3974     }
3975 #endif
3976 }
3977
3978 void
3979 GetMoveListEvent()
3980 {
3981     char buf[MSG_SIZ];
3982     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983         ics_getting_history = H_REQUESTED;
3984         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3985         SendToICS(buf);
3986     }
3987 }
3988
3989 void
3990 AnalysisPeriodicEvent(force)
3991      int force;
3992 {
3993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994          && !force) || !appData.periodicUpdates)
3995       return;
3996
3997     /* Send . command to Crafty to collect stats */
3998     SendToProgram(".\n", &first);
3999
4000     /* Don't send another until we get a response (this makes
4001        us stop sending to old Crafty's which don't understand
4002        the "." command (sending illegal cmds resets node count & time,
4003        which looks bad)) */
4004     programStats.ok_to_send = 0;
4005 }
4006
4007 void ics_update_width(new_width)
4008         int new_width;
4009 {
4010         ics_printf("set width %d\n", new_width);
4011 }
4012
4013 void
4014 SendMoveToProgram(moveNum, cps)
4015      int moveNum;
4016      ChessProgramState *cps;
4017 {
4018     char buf[MSG_SIZ];
4019
4020     if (cps->useUsermove) {
4021       SendToProgram("usermove ", cps);
4022     }
4023     if (cps->useSAN) {
4024       char *space;
4025       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4026         int len = space - parseList[moveNum];
4027         memcpy(buf, parseList[moveNum], len);
4028         buf[len++] = '\n';
4029         buf[len] = NULLCHAR;
4030       } else {
4031         sprintf(buf, "%s\n", parseList[moveNum]);
4032       }
4033       SendToProgram(buf, cps);
4034     } else {
4035       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4036         AlphaRank(moveList[moveNum], 4);
4037         SendToProgram(moveList[moveNum], cps);
4038         AlphaRank(moveList[moveNum], 4); // and back
4039       } else
4040       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4041        * the engine. It would be nice to have a better way to identify castle 
4042        * moves here. */
4043       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4044                                                                          && cps->useOOCastle) {
4045         int fromX = moveList[moveNum][0] - AAA; 
4046         int fromY = moveList[moveNum][1] - ONE;
4047         int toX = moveList[moveNum][2] - AAA; 
4048         int toY = moveList[moveNum][3] - ONE;
4049         if((boards[moveNum][fromY][fromX] == WhiteKing 
4050             && boards[moveNum][toY][toX] == WhiteRook)
4051            || (boards[moveNum][fromY][fromX] == BlackKing 
4052                && boards[moveNum][toY][toX] == BlackRook)) {
4053           if(toX > fromX) SendToProgram("O-O\n", cps);
4054           else SendToProgram("O-O-O\n", cps);
4055         }
4056         else SendToProgram(moveList[moveNum], cps);
4057       }
4058       else SendToProgram(moveList[moveNum], cps);
4059       /* End of additions by Tord */
4060     }
4061
4062     /* [HGM] setting up the opening has brought engine in force mode! */
4063     /*       Send 'go' if we are in a mode where machine should play. */
4064     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4065         (gameMode == TwoMachinesPlay   ||
4066 #ifdef ZIPPY
4067          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4068 #endif
4069          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4070         SendToProgram("go\n", cps);
4071   if (appData.debugMode) {
4072     fprintf(debugFP, "(extra)\n");
4073   }
4074     }
4075     setboardSpoiledMachineBlack = 0;
4076 }
4077
4078 void
4079 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4080      ChessMove moveType;
4081      int fromX, fromY, toX, toY;
4082 {
4083     char user_move[MSG_SIZ];
4084
4085     switch (moveType) {
4086       default:
4087         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4088                 (int)moveType, fromX, fromY, toX, toY);
4089         DisplayError(user_move + strlen("say "), 0);
4090         break;
4091       case WhiteKingSideCastle:
4092       case BlackKingSideCastle:
4093       case WhiteQueenSideCastleWild:
4094       case BlackQueenSideCastleWild:
4095       /* PUSH Fabien */
4096       case WhiteHSideCastleFR:
4097       case BlackHSideCastleFR:
4098       /* POP Fabien */
4099         sprintf(user_move, "o-o\n");
4100         break;
4101       case WhiteQueenSideCastle:
4102       case BlackQueenSideCastle:
4103       case WhiteKingSideCastleWild:
4104       case BlackKingSideCastleWild:
4105       /* PUSH Fabien */
4106       case WhiteASideCastleFR:
4107       case BlackASideCastleFR:
4108       /* POP Fabien */
4109         sprintf(user_move, "o-o-o\n");
4110         break;
4111       case WhitePromotionQueen:
4112       case BlackPromotionQueen:
4113       case WhitePromotionRook:
4114       case BlackPromotionRook:
4115       case WhitePromotionBishop:
4116       case BlackPromotionBishop:
4117       case WhitePromotionKnight:
4118       case BlackPromotionKnight:
4119       case WhitePromotionKing:
4120       case BlackPromotionKing:
4121       case WhitePromotionChancellor:
4122       case BlackPromotionChancellor:
4123       case WhitePromotionArchbishop:
4124       case BlackPromotionArchbishop:
4125         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4126             sprintf(user_move, "%c%c%c%c=%c\n",
4127                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4128                 PieceToChar(WhiteFerz));
4129         else if(gameInfo.variant == VariantGreat)
4130             sprintf(user_move, "%c%c%c%c=%c\n",
4131                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4132                 PieceToChar(WhiteMan));
4133         else
4134             sprintf(user_move, "%c%c%c%c=%c\n",
4135                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4136                 PieceToChar(PromoPiece(moveType)));
4137         break;
4138       case WhiteDrop:
4139       case BlackDrop:
4140         sprintf(user_move, "%c@%c%c\n",
4141                 ToUpper(PieceToChar((ChessSquare) fromX)),
4142                 AAA + toX, ONE + toY);
4143         break;
4144       case NormalMove:
4145       case WhiteCapturesEnPassant:
4146       case BlackCapturesEnPassant:
4147       case IllegalMove:  /* could be a variant we don't quite understand */
4148         sprintf(user_move, "%c%c%c%c\n",
4149                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4150         break;
4151     }
4152     SendToICS(user_move);
4153     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4154         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4155 }
4156
4157 void
4158 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4159      int rf, ff, rt, ft;
4160      char promoChar;
4161      char move[7];
4162 {
4163     if (rf == DROP_RANK) {
4164         sprintf(move, "%c@%c%c\n",
4165                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4166     } else {
4167         if (promoChar == 'x' || promoChar == NULLCHAR) {
4168             sprintf(move, "%c%c%c%c\n",
4169                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4170         } else {
4171             sprintf(move, "%c%c%c%c%c\n",
4172                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4173         }
4174     }
4175 }
4176
4177 void
4178 ProcessICSInitScript(f)
4179      FILE *f;
4180 {
4181     char buf[MSG_SIZ];
4182
4183     while (fgets(buf, MSG_SIZ, f)) {
4184         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4185     }
4186
4187     fclose(f);
4188 }
4189
4190
4191 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4192 void
4193 AlphaRank(char *move, int n)
4194 {
4195 //    char *p = move, c; int x, y;
4196
4197     if (appData.debugMode) {
4198         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4199     }
4200
4201     if(move[1]=='*' && 
4202        move[2]>='0' && move[2]<='9' &&
4203        move[3]>='a' && move[3]<='x'    ) {
4204         move[1] = '@';
4205         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4206         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4207     } else
4208     if(move[0]>='0' && move[0]<='9' &&
4209        move[1]>='a' && move[1]<='x' &&
4210        move[2]>='0' && move[2]<='9' &&
4211        move[3]>='a' && move[3]<='x'    ) {
4212         /* input move, Shogi -> normal */
4213         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4214         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4215         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4216         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4217     } else
4218     if(move[1]=='@' &&
4219        move[3]>='0' && move[3]<='9' &&
4220        move[2]>='a' && move[2]<='x'    ) {
4221         move[1] = '*';
4222         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4223         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4224     } else
4225     if(
4226        move[0]>='a' && move[0]<='x' &&
4227        move[3]>='0' && move[3]<='9' &&
4228        move[2]>='a' && move[2]<='x'    ) {
4229          /* output move, normal -> Shogi */
4230         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4231         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4232         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4233         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4234         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4235     }
4236     if (appData.debugMode) {
4237         fprintf(debugFP, "   out = '%s'\n", move);
4238     }
4239 }
4240
4241 /* Parser for moves from gnuchess, ICS, or user typein box */
4242 Boolean
4243 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4244      char *move;
4245      int moveNum;
4246      ChessMove *moveType;
4247      int *fromX, *fromY, *toX, *toY;
4248      char *promoChar;
4249 {       
4250     if (appData.debugMode) {
4251         fprintf(debugFP, "move to parse: %s\n", move);
4252     }
4253     *moveType = yylexstr(moveNum, move);
4254
4255     switch (*moveType) {
4256       case WhitePromotionChancellor:
4257       case BlackPromotionChancellor:
4258       case WhitePromotionArchbishop:
4259       case BlackPromotionArchbishop:
4260       case WhitePromotionQueen:
4261       case BlackPromotionQueen:
4262       case WhitePromotionRook:
4263       case BlackPromotionRook:
4264       case WhitePromotionBishop:
4265       case BlackPromotionBishop:
4266       case WhitePromotionKnight:
4267       case BlackPromotionKnight:
4268       case WhitePromotionKing:
4269       case BlackPromotionKing:
4270       case NormalMove:
4271       case WhiteCapturesEnPassant:
4272       case BlackCapturesEnPassant:
4273       case WhiteKingSideCastle:
4274       case WhiteQueenSideCastle:
4275       case BlackKingSideCastle:
4276       case BlackQueenSideCastle:
4277       case WhiteKingSideCastleWild:
4278       case WhiteQueenSideCastleWild:
4279       case BlackKingSideCastleWild:
4280       case BlackQueenSideCastleWild:
4281       /* Code added by Tord: */
4282       case WhiteHSideCastleFR:
4283       case WhiteASideCastleFR:
4284       case BlackHSideCastleFR:
4285       case BlackASideCastleFR:
4286       /* End of code added by Tord */
4287       case IllegalMove:         /* bug or odd chess variant */
4288         *fromX = currentMoveString[0] - AAA;
4289         *fromY = currentMoveString[1] - ONE;
4290         *toX = currentMoveString[2] - AAA;
4291         *toY = currentMoveString[3] - ONE;
4292         *promoChar = currentMoveString[4];
4293         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4294             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4297     }
4298             *fromX = *fromY = *toX = *toY = 0;
4299             return FALSE;
4300         }
4301         if (appData.testLegality) {
4302           return (*moveType != IllegalMove);
4303         } else {
4304           return !(fromX == fromY && toX == toY);
4305         }
4306
4307       case WhiteDrop:
4308       case BlackDrop:
4309         *fromX = *moveType == WhiteDrop ?
4310           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4311           (int) CharToPiece(ToLower(currentMoveString[0]));
4312         *fromY = DROP_RANK;
4313         *toX = currentMoveString[2] - AAA;
4314         *toY = currentMoveString[3] - ONE;
4315         *promoChar = NULLCHAR;
4316         return TRUE;
4317
4318       case AmbiguousMove:
4319       case ImpossibleMove:
4320       case (ChessMove) 0:       /* end of file */
4321       case ElapsedTime:
4322       case Comment:
4323       case PGNTag:
4324       case NAG:
4325       case WhiteWins:
4326       case BlackWins:
4327       case GameIsDrawn:
4328       default:
4329     if (appData.debugMode) {
4330         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4331     }
4332         /* bug? */
4333         *fromX = *fromY = *toX = *toY = 0;
4334         *promoChar = NULLCHAR;
4335         return FALSE;
4336     }
4337 }
4338
4339 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4340 // All positions will have equal probability, but the current method will not provide a unique
4341 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4342 #define DARK 1
4343 #define LITE 2
4344 #define ANY 3
4345
4346 int squaresLeft[4];
4347 int piecesLeft[(int)BlackPawn];
4348 int seed, nrOfShuffles;
4349
4350 void GetPositionNumber()
4351 {       // sets global variable seed
4352         int i;
4353
4354         seed = appData.defaultFrcPosition;
4355         if(seed < 0) { // randomize based on time for negative FRC position numbers
4356                 for(i=0; i<50; i++) seed += random();
4357                 seed = random() ^ random() >> 8 ^ random() << 8;
4358                 if(seed<0) seed = -seed;
4359         }
4360 }
4361
4362 int put(Board board, int pieceType, int rank, int n, int shade)
4363 // put the piece on the (n-1)-th empty squares of the given shade
4364 {
4365         int i;
4366
4367         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4368                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4369                         board[rank][i] = (ChessSquare) pieceType;
4370                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4371                         squaresLeft[ANY]--;
4372                         piecesLeft[pieceType]--; 
4373                         return i;
4374                 }
4375         }
4376         return -1;
4377 }
4378
4379
4380 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4381 // calculate where the next piece goes, (any empty square), and put it there
4382 {
4383         int i;
4384
4385         i = seed % squaresLeft[shade];
4386         nrOfShuffles *= squaresLeft[shade];
4387         seed /= squaresLeft[shade];
4388         put(board, pieceType, rank, i, shade);
4389 }
4390
4391 void AddTwoPieces(Board board, int pieceType, int rank)
4392 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4393 {
4394         int i, n=squaresLeft[ANY], j=n-1, k;
4395
4396         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4397         i = seed % k;  // pick one
4398         nrOfShuffles *= k;
4399         seed /= k;
4400         while(i >= j) i -= j--;
4401         j = n - 1 - j; i += j;
4402         put(board, pieceType, rank, j, ANY);
4403         put(board, pieceType, rank, i, ANY);
4404 }
4405
4406 void SetUpShuffle(Board board, int number)
4407 {
4408         int i, p, first=1;
4409
4410         GetPositionNumber(); nrOfShuffles = 1;
4411
4412         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4413         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4414         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4415
4416         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4417
4418         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4419             p = (int) board[0][i];
4420             if(p < (int) BlackPawn) piecesLeft[p] ++;
4421             board[0][i] = EmptySquare;
4422         }
4423
4424         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4425             // shuffles restricted to allow normal castling put KRR first
4426             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4427                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4428             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4429                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4430             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4431                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4432             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4433                 put(board, WhiteRook, 0, 0, ANY);
4434             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4435         }
4436
4437         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4438             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4439             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4440                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4441                 while(piecesLeft[p] >= 2) {
4442                     AddOnePiece(board, p, 0, LITE);
4443                     AddOnePiece(board, p, 0, DARK);
4444                 }
4445                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4446             }
4447
4448         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4449             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4450             // but we leave King and Rooks for last, to possibly obey FRC restriction
4451             if(p == (int)WhiteRook) continue;
4452             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4453             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4454         }
4455
4456         // now everything is placed, except perhaps King (Unicorn) and Rooks
4457
4458         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4459             // Last King gets castling rights
4460             while(piecesLeft[(int)WhiteUnicorn]) {
4461                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4463             }
4464
4465             while(piecesLeft[(int)WhiteKing]) {
4466                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4467                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4468             }
4469
4470
4471         } else {
4472             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4473             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4474         }
4475
4476         // Only Rooks can be left; simply place them all
4477         while(piecesLeft[(int)WhiteRook]) {
4478                 i = put(board, WhiteRook, 0, 0, ANY);
4479                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4480                         if(first) {
4481                                 first=0;
4482                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4483                         }
4484                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4485                 }
4486         }
4487         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4488             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4489         }
4490
4491         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4492 }
4493
4494 int SetCharTable( char *table, const char * map )
4495 /* [HGM] moved here from winboard.c because of its general usefulness */
4496 /*       Basically a safe strcpy that uses the last character as King */
4497 {
4498     int result = FALSE; int NrPieces;
4499
4500     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4501                     && NrPieces >= 12 && !(NrPieces&1)) {
4502         int i; /* [HGM] Accept even length from 12 to 34 */
4503
4504         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4505         for( i=0; i<NrPieces/2-1; i++ ) {
4506             table[i] = map[i];
4507             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4508         }
4509         table[(int) WhiteKing]  = map[NrPieces/2-1];
4510         table[(int) BlackKing]  = map[NrPieces-1];
4511
4512         result = TRUE;
4513     }
4514
4515     return result;
4516 }
4517
4518 void Prelude(Board board)
4519 {       // [HGM] superchess: random selection of exo-pieces
4520         int i, j, k; ChessSquare p; 
4521         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4522
4523         GetPositionNumber(); // use FRC position number
4524
4525         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4526             SetCharTable(pieceToChar, appData.pieceToCharTable);
4527             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4528                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4529         }
4530
4531         j = seed%4;                 seed /= 4; 
4532         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4533         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4534         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4535         j = seed%3 + (seed%3 >= j); seed /= 3; 
4536         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4537         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4538         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4539         j = seed%3;                 seed /= 3; 
4540         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4541         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4542         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4543         j = seed%2 + (seed%2 >= j); seed /= 2; 
4544         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4545         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4546         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4547         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4548         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4549         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4550         put(board, exoPieces[0],    0, 0, ANY);
4551         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4552 }
4553
4554 void
4555 InitPosition(redraw)
4556      int redraw;
4557 {
4558     ChessSquare (* pieces)[BOARD_SIZE];
4559     int i, j, pawnRow, overrule,
4560     oldx = gameInfo.boardWidth,
4561     oldy = gameInfo.boardHeight,
4562     oldh = gameInfo.holdingsWidth,
4563     oldv = gameInfo.variant;
4564
4565     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4566
4567     /* [AS] Initialize pv info list [HGM] and game status */
4568     {
4569         for( i=0; i<MAX_MOVES; i++ ) {
4570             pvInfoList[i].depth = 0;
4571             epStatus[i]=EP_NONE;
4572             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4573         }
4574
4575         initialRulePlies = 0; /* 50-move counter start */
4576
4577         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4578         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4579     }
4580
4581     
4582     /* [HGM] logic here is completely changed. In stead of full positions */
4583     /* the initialized data only consist of the two backranks. The switch */
4584     /* selects which one we will use, which is than copied to the Board   */
4585     /* initialPosition, which for the rest is initialized by Pawns and    */
4586     /* empty squares. This initial position is then copied to boards[0],  */
4587     /* possibly after shuffling, so that it remains available.            */
4588
4589     gameInfo.holdingsWidth = 0; /* default board sizes */
4590     gameInfo.boardWidth    = 8;
4591     gameInfo.boardHeight   = 8;
4592     gameInfo.holdingsSize  = 0;
4593     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4594     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4595     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4596
4597     switch (gameInfo.variant) {
4598     case VariantFischeRandom:
4599       shuffleOpenings = TRUE;
4600     default:
4601       pieces = FIDEArray;
4602       break;
4603     case VariantShatranj:
4604       pieces = ShatranjArray;
4605       nrCastlingRights = 0;
4606       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4607       break;
4608     case VariantTwoKings:
4609       pieces = twoKingsArray;
4610       break;
4611     case VariantCapaRandom:
4612       shuffleOpenings = TRUE;
4613     case VariantCapablanca:
4614       pieces = CapablancaArray;
4615       gameInfo.boardWidth = 10;
4616       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4617       break;
4618     case VariantGothic:
4619       pieces = GothicArray;
4620       gameInfo.boardWidth = 10;
4621       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4622       break;
4623     case VariantJanus:
4624       pieces = JanusArray;
4625       gameInfo.boardWidth = 10;
4626       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4627       nrCastlingRights = 6;
4628         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4629         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4630         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4631         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4632         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4633         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4634       break;
4635     case VariantFalcon:
4636       pieces = FalconArray;
4637       gameInfo.boardWidth = 10;
4638       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4639       break;
4640     case VariantXiangqi:
4641       pieces = XiangqiArray;
4642       gameInfo.boardWidth  = 9;
4643       gameInfo.boardHeight = 10;
4644       nrCastlingRights = 0;
4645       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4646       break;
4647     case VariantShogi:
4648       pieces = ShogiArray;
4649       gameInfo.boardWidth  = 9;
4650       gameInfo.boardHeight = 9;
4651       gameInfo.holdingsSize = 7;
4652       nrCastlingRights = 0;
4653       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4654       break;
4655     case VariantCourier:
4656       pieces = CourierArray;
4657       gameInfo.boardWidth  = 12;
4658       nrCastlingRights = 0;
4659       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4660       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4661       break;
4662     case VariantKnightmate:
4663       pieces = KnightmateArray;
4664       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4665       break;
4666     case VariantFairy:
4667       pieces = fairyArray;
4668       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4669       break;
4670     case VariantGreat:
4671       pieces = GreatArray;
4672       gameInfo.boardWidth = 10;
4673       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4674       gameInfo.holdingsSize = 8;
4675       break;
4676     case VariantSuper:
4677       pieces = FIDEArray;
4678       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4679       gameInfo.holdingsSize = 8;
4680       startedFromSetupPosition = TRUE;
4681       break;
4682     case VariantCrazyhouse:
4683     case VariantBughouse:
4684       pieces = FIDEArray;
4685       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4686       gameInfo.holdingsSize = 5;
4687       break;
4688     case VariantWildCastle:
4689       pieces = FIDEArray;
4690       /* !!?shuffle with kings guaranteed to be on d or e file */
4691       shuffleOpenings = 1;
4692       break;
4693     case VariantNoCastle:
4694       pieces = FIDEArray;
4695       nrCastlingRights = 0;
4696       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4697       /* !!?unconstrained back-rank shuffle */
4698       shuffleOpenings = 1;
4699       break;
4700     }
4701
4702     overrule = 0;
4703     if(appData.NrFiles >= 0) {
4704         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4705         gameInfo.boardWidth = appData.NrFiles;
4706     }
4707     if(appData.NrRanks >= 0) {
4708         gameInfo.boardHeight = appData.NrRanks;
4709     }
4710     if(appData.holdingsSize >= 0) {
4711         i = appData.holdingsSize;
4712         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4713         gameInfo.holdingsSize = i;
4714     }
4715     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4716     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4717         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4718
4719     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4720     if(pawnRow < 1) pawnRow = 1;
4721
4722     /* User pieceToChar list overrules defaults */
4723     if(appData.pieceToCharTable != NULL)
4724         SetCharTable(pieceToChar, appData.pieceToCharTable);
4725
4726     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4727
4728         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4729             s = (ChessSquare) 0; /* account holding counts in guard band */
4730         for( i=0; i<BOARD_HEIGHT; i++ )
4731             initialPosition[i][j] = s;
4732
4733         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4734         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4735         initialPosition[pawnRow][j] = WhitePawn;
4736         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4737         if(gameInfo.variant == VariantXiangqi) {
4738             if(j&1) {
4739                 initialPosition[pawnRow][j] = 
4740                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4741                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4742                    initialPosition[2][j] = WhiteCannon;
4743                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4744                 }
4745             }
4746         }
4747         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4748     }
4749     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4750
4751             j=BOARD_LEFT+1;
4752             initialPosition[1][j] = WhiteBishop;
4753             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4754             j=BOARD_RGHT-2;
4755             initialPosition[1][j] = WhiteRook;
4756             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4757     }
4758
4759     if( nrCastlingRights == -1) {
4760         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4761         /*       This sets default castling rights from none to normal corners   */
4762         /* Variants with other castling rights must set them themselves above    */
4763         nrCastlingRights = 6;
4764        
4765         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4766         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4767         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4768         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4769         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4770         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4771      }
4772
4773      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4774      if(gameInfo.variant == VariantGreat) { // promotion commoners
4775         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4776         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4777         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4778         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4779      }
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4782   }
4783     if(shuffleOpenings) {
4784         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4785         startedFromSetupPosition = TRUE;
4786     }
4787     if(startedFromPositionFile) {
4788       /* [HGM] loadPos: use PositionFile for every new game */
4789       CopyBoard(initialPosition, filePosition);
4790       for(i=0; i<nrCastlingRights; i++)
4791           castlingRights[0][i] = initialRights[i] = fileRights[i];
4792       startedFromSetupPosition = TRUE;
4793     }
4794
4795     CopyBoard(boards[0], initialPosition);
4796
4797     if(oldx != gameInfo.boardWidth ||
4798        oldy != gameInfo.boardHeight ||
4799        oldh != gameInfo.holdingsWidth
4800 #ifdef GOTHIC
4801        || oldv == VariantGothic ||        // For licensing popups
4802        gameInfo.variant == VariantGothic
4803 #endif
4804 #ifdef FALCON
4805        || oldv == VariantFalcon ||
4806        gameInfo.variant == VariantFalcon
4807 #endif
4808                                          )
4809             InitDrawingSizes(-2 ,0);
4810
4811     if (redraw)
4812       DrawPosition(TRUE, boards[currentMove]);
4813 }
4814
4815 void
4816 SendBoard(cps, moveNum)
4817      ChessProgramState *cps;
4818      int moveNum;
4819 {
4820     char message[MSG_SIZ];
4821     
4822     if (cps->useSetboard) {
4823       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4824       sprintf(message, "setboard %s\n", fen);
4825       SendToProgram(message, cps);
4826       free(fen);
4827
4828     } else {
4829       ChessSquare *bp;
4830       int i, j;
4831       /* Kludge to set black to move, avoiding the troublesome and now
4832        * deprecated "black" command.
4833        */
4834       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4835
4836       SendToProgram("edit\n", cps);
4837       SendToProgram("#\n", cps);
4838       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4839         bp = &boards[moveNum][i][BOARD_LEFT];
4840         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4841           if ((int) *bp < (int) BlackPawn) {
4842             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4843                     AAA + j, ONE + i);
4844             if(message[0] == '+' || message[0] == '~') {
4845                 sprintf(message, "%c%c%c+\n",
4846                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4847                         AAA + j, ONE + i);
4848             }
4849             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4850                 message[1] = BOARD_RGHT   - 1 - j + '1';
4851                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4852             }
4853             SendToProgram(message, cps);
4854           }
4855         }
4856       }
4857     
4858       SendToProgram("c\n", cps);
4859       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4860         bp = &boards[moveNum][i][BOARD_LEFT];
4861         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4862           if (((int) *bp != (int) EmptySquare)
4863               && ((int) *bp >= (int) BlackPawn)) {
4864             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4865                     AAA + j, ONE + i);
4866             if(message[0] == '+' || message[0] == '~') {
4867                 sprintf(message, "%c%c%c+\n",
4868                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4869                         AAA + j, ONE + i);
4870             }
4871             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4872                 message[1] = BOARD_RGHT   - 1 - j + '1';
4873                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4874             }
4875             SendToProgram(message, cps);
4876           }
4877         }
4878       }
4879     
4880       SendToProgram(".\n", cps);
4881     }
4882     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4883 }
4884
4885 int
4886 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4887 {
4888     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4889     /* [HGM] add Shogi promotions */
4890     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4891     ChessSquare piece;
4892     ChessMove moveType;
4893     Boolean premove;
4894
4895     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4896     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4897
4898     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4899       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4900         return FALSE;
4901
4902     piece = boards[currentMove][fromY][fromX];
4903     if(gameInfo.variant == VariantShogi) {
4904         promotionZoneSize = 3;
4905         highestPromotingPiece = (int)WhiteFerz;
4906     }
4907
4908     // next weed out all moves that do not touch the promotion zone at all
4909     if((int)piece >= BlackPawn) {
4910         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4911              return FALSE;
4912         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4913     } else {
4914         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4915            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4916     }
4917
4918     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4919
4920     // weed out mandatory Shogi promotions
4921     if(gameInfo.variant == VariantShogi) {
4922         if(piece >= BlackPawn) {
4923             if(toY == 0 && piece == BlackPawn ||
4924                toY == 0 && piece == BlackQueen ||
4925                toY <= 1 && piece == BlackKnight) {
4926                 *promoChoice = '+';
4927                 return FALSE;
4928             }
4929         } else {
4930             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4931                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4932                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4933                 *promoChoice = '+';
4934                 return FALSE;
4935             }
4936         }
4937     }
4938
4939     // weed out obviously illegal Pawn moves
4940     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4941         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4942         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4943         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4944         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4945         // note we are not allowed to test for valid (non-)capture, due to premove
4946     }
4947
4948     // we either have a choice what to promote to, or (in Shogi) whether to promote
4949     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4950         *promoChoice = PieceToChar(BlackFerz);  // no choice
4951         return FALSE;
4952     }
4953     if(appData.alwaysPromoteToQueen) { // predetermined
4954         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4955              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4956         else *promoChoice = PieceToChar(BlackQueen);
4957         return FALSE;
4958     }
4959
4960     // suppress promotion popup on illegal moves that are not premoves
4961     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4962               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
4963     if(appData.testLegality && !premove) {
4964         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4965                         epStatus[currentMove], castlingRights[currentMove],
4966                         fromY, fromX, toY, toX, NULLCHAR);
4967         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
4968            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4969             return FALSE;
4970     }
4971
4972     return TRUE;
4973 }
4974
4975 int
4976 InPalace(row, column)
4977      int row, column;
4978 {   /* [HGM] for Xiangqi */
4979     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4980          column < (BOARD_WIDTH + 4)/2 &&
4981          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4982     return FALSE;
4983 }
4984
4985 int
4986 PieceForSquare (x, y)
4987      int x;
4988      int y;
4989 {
4990   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4991      return -1;
4992   else
4993      return boards[currentMove][y][x];
4994 }
4995
4996 int
4997 OKToStartUserMove(x, y)
4998      int x, y;
4999 {
5000     ChessSquare from_piece;
5001     int white_piece;
5002
5003     if (matchMode) return FALSE;
5004     if (gameMode == EditPosition) return TRUE;
5005
5006     if (x >= 0 && y >= 0)
5007       from_piece = boards[currentMove][y][x];
5008     else
5009       from_piece = EmptySquare;
5010
5011     if (from_piece == EmptySquare) return FALSE;
5012
5013     white_piece = (int)from_piece >= (int)WhitePawn &&
5014       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5015
5016     switch (gameMode) {
5017       case PlayFromGameFile:
5018       case AnalyzeFile:
5019       case TwoMachinesPlay:
5020       case EndOfGame:
5021         return FALSE;
5022
5023       case IcsObserving:
5024       case IcsIdle:
5025         return FALSE;
5026
5027       case MachinePlaysWhite:
5028       case IcsPlayingBlack:
5029         if (appData.zippyPlay) return FALSE;
5030         if (white_piece) {
5031             DisplayMoveError(_("You are playing Black"));
5032             return FALSE;
5033         }
5034         break;
5035
5036       case MachinePlaysBlack:
5037       case IcsPlayingWhite:
5038         if (appData.zippyPlay) return FALSE;
5039         if (!white_piece) {
5040             DisplayMoveError(_("You are playing White"));
5041             return FALSE;
5042         }
5043         break;
5044
5045       case EditGame:
5046         if (!white_piece && WhiteOnMove(currentMove)) {
5047             DisplayMoveError(_("It is White's turn"));
5048             return FALSE;
5049         }           
5050         if (white_piece && !WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is Black's turn"));
5052             return FALSE;
5053         }           
5054         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5055             /* Editing correspondence game history */
5056             /* Could disallow this or prompt for confirmation */
5057             cmailOldMove = -1;
5058         }
5059         if (currentMove < forwardMostMove) {
5060             /* Discarding moves */
5061             /* Could prompt for confirmation here,
5062                but I don't think that's such a good idea */
5063             forwardMostMove = currentMove;
5064         }
5065         break;
5066
5067       case BeginningOfGame:
5068         if (appData.icsActive) return FALSE;
5069         if (!appData.noChessProgram) {
5070             if (!white_piece) {
5071                 DisplayMoveError(_("You are playing White"));
5072                 return FALSE;
5073             }
5074         }
5075         break;
5076         
5077       case Training:
5078         if (!white_piece && WhiteOnMove(currentMove)) {
5079             DisplayMoveError(_("It is White's turn"));
5080             return FALSE;
5081         }           
5082         if (white_piece && !WhiteOnMove(currentMove)) {
5083             DisplayMoveError(_("It is Black's turn"));
5084             return FALSE;
5085         }           
5086         break;
5087
5088       default:
5089       case IcsExamining:
5090         break;
5091     }
5092     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5093         && gameMode != AnalyzeFile && gameMode != Training) {
5094         DisplayMoveError(_("Displayed position is not current"));
5095         return FALSE;
5096     }
5097     return TRUE;
5098 }
5099
5100 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5101 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5102 int lastLoadGameUseList = FALSE;
5103 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5104 ChessMove lastLoadGameStart = (ChessMove) 0;
5105
5106 ChessMove
5107 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5108      int fromX, fromY, toX, toY;
5109      int promoChar;
5110      Boolean captureOwn;
5111 {
5112     ChessMove moveType;
5113     ChessSquare pdown, pup;
5114
5115     /* Check if the user is playing in turn.  This is complicated because we
5116        let the user "pick up" a piece before it is his turn.  So the piece he
5117        tried to pick up may have been captured by the time he puts it down!
5118        Therefore we use the color the user is supposed to be playing in this
5119        test, not the color of the piece that is currently on the starting
5120        square---except in EditGame mode, where the user is playing both
5121        sides; fortunately there the capture race can't happen.  (It can
5122        now happen in IcsExamining mode, but that's just too bad.  The user
5123        will get a somewhat confusing message in that case.)
5124        */
5125
5126     switch (gameMode) {
5127       case PlayFromGameFile:
5128       case AnalyzeFile:
5129       case TwoMachinesPlay:
5130       case EndOfGame:
5131       case IcsObserving:
5132       case IcsIdle:
5133         /* We switched into a game mode where moves are not accepted,
5134            perhaps while the mouse button was down. */
5135         return ImpossibleMove;
5136
5137       case MachinePlaysWhite:
5138         /* User is moving for Black */
5139         if (WhiteOnMove(currentMove)) {
5140             DisplayMoveError(_("It is White's turn"));
5141             return ImpossibleMove;
5142         }
5143         break;
5144
5145       case MachinePlaysBlack:
5146         /* User is moving for White */
5147         if (!WhiteOnMove(currentMove)) {
5148             DisplayMoveError(_("It is Black's turn"));
5149             return ImpossibleMove;
5150         }
5151         break;
5152
5153       case EditGame:
5154       case IcsExamining:
5155       case BeginningOfGame:
5156       case AnalyzeMode:
5157       case Training:
5158         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5159             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5160             /* User is moving for Black */
5161             if (WhiteOnMove(currentMove)) {
5162                 DisplayMoveError(_("It is White's turn"));
5163                 return ImpossibleMove;
5164             }
5165         } else {
5166             /* User is moving for White */
5167             if (!WhiteOnMove(currentMove)) {
5168                 DisplayMoveError(_("It is Black's turn"));
5169                 return ImpossibleMove;
5170             }
5171         }
5172         break;
5173
5174       case IcsPlayingBlack:
5175         /* User is moving for Black */
5176         if (WhiteOnMove(currentMove)) {
5177             if (!appData.premove) {
5178                 DisplayMoveError(_("It is White's turn"));
5179             } else if (toX >= 0 && toY >= 0) {
5180                 premoveToX = toX;
5181                 premoveToY = toY;
5182                 premoveFromX = fromX;
5183                 premoveFromY = fromY;
5184                 premovePromoChar = promoChar;
5185                 gotPremove = 1;
5186                 if (appData.debugMode) 
5187                     fprintf(debugFP, "Got premove: fromX %d,"
5188                             "fromY %d, toX %d, toY %d\n",
5189                             fromX, fromY, toX, toY);
5190             }
5191             return ImpossibleMove;
5192         }
5193         break;
5194
5195       case IcsPlayingWhite:
5196         /* User is moving for White */
5197         if (!WhiteOnMove(currentMove)) {
5198             if (!appData.premove) {
5199                 DisplayMoveError(_("It is Black's turn"));
5200             } else if (toX >= 0 && toY >= 0) {
5201                 premoveToX = toX;
5202                 premoveToY = toY;
5203                 premoveFromX = fromX;
5204                 premoveFromY = fromY;
5205                 premovePromoChar = promoChar;
5206                 gotPremove = 1;
5207                 if (appData.debugMode) 
5208                     fprintf(debugFP, "Got premove: fromX %d,"
5209                             "fromY %d, toX %d, toY %d\n",
5210                             fromX, fromY, toX, toY);
5211             }
5212             return ImpossibleMove;
5213         }
5214         break;
5215
5216       default:
5217         break;
5218
5219       case EditPosition:
5220         /* EditPosition, empty square, or different color piece;
5221            click-click move is possible */
5222         if (toX == -2 || toY == -2) {
5223             boards[0][fromY][fromX] = EmptySquare;
5224             return AmbiguousMove;
5225         } else if (toX >= 0 && toY >= 0) {
5226             boards[0][toY][toX] = boards[0][fromY][fromX];
5227             boards[0][fromY][fromX] = EmptySquare;
5228             return AmbiguousMove;
5229         }
5230         return ImpossibleMove;
5231     }
5232
5233     pdown = boards[currentMove][fromY][fromX];
5234     pup = boards[currentMove][toY][toX];
5235
5236     /* [HGM] If move started in holdings, it means a drop */
5237     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5238          if( pup != EmptySquare ) return ImpossibleMove;
5239          if(appData.testLegality) {
5240              /* it would be more logical if LegalityTest() also figured out
5241               * which drops are legal. For now we forbid pawns on back rank.
5242               * Shogi is on its own here...
5243               */
5244              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5245                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5246                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5247          }
5248          return WhiteDrop; /* Not needed to specify white or black yet */
5249     }
5250
5251     userOfferedDraw = FALSE;
5252         
5253     /* [HGM] always test for legality, to get promotion info */
5254     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5255                           epStatus[currentMove], castlingRights[currentMove],
5256                                          fromY, fromX, toY, toX, promoChar);
5257     /* [HGM] but possibly ignore an IllegalMove result */
5258     if (appData.testLegality) {
5259         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5260             DisplayMoveError(_("Illegal move"));
5261             return ImpossibleMove;
5262         }
5263     }
5264 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5265     return moveType;
5266     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5267        function is made into one that returns an OK move type if FinishMove
5268        should be called. This to give the calling driver routine the
5269        opportunity to finish the userMove input with a promotion popup,
5270        without bothering the user with this for invalid or illegal moves */
5271
5272 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5273 }
5274
5275 /* Common tail of UserMoveEvent and DropMenuEvent */
5276 int
5277 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5278      ChessMove moveType;
5279      int fromX, fromY, toX, toY;
5280      /*char*/int promoChar;
5281 {
5282     char *bookHit = 0;
5283 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5284     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5285         // [HGM] superchess: suppress promotions to non-available piece
5286         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5287         if(WhiteOnMove(currentMove)) {
5288             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5289         } else {
5290             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5291         }
5292     }
5293
5294     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5295        move type in caller when we know the move is a legal promotion */
5296     if(moveType == NormalMove && promoChar)
5297         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5298 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5299     /* [HGM] convert drag-and-drop piece drops to standard form */
5300     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5301          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5302            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5303                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5304 //         fromX = boards[currentMove][fromY][fromX];
5305            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5306            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5307            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5308            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5309          fromY = DROP_RANK;
5310     }
5311
5312     /* [HGM] <popupFix> The following if has been moved here from
5313        UserMoveEvent(). Because it seemed to belon here (why not allow
5314        piece drops in training games?), and because it can only be
5315        performed after it is known to what we promote. */
5316     if (gameMode == Training) {
5317       /* compare the move played on the board to the next move in the
5318        * game. If they match, display the move and the opponent's response. 
5319        * If they don't match, display an error message.
5320        */
5321       int saveAnimate;
5322       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5323       CopyBoard(testBoard, boards[currentMove]);
5324       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5325
5326       if (CompareBoards(testBoard, boards[currentMove+1])) {
5327         ForwardInner(currentMove+1);
5328
5329         /* Autoplay the opponent's response.
5330          * if appData.animate was TRUE when Training mode was entered,
5331          * the response will be animated.
5332          */
5333         saveAnimate = appData.animate;
5334         appData.animate = animateTraining;
5335         ForwardInner(currentMove+1);
5336         appData.animate = saveAnimate;
5337
5338         /* check for the end of the game */
5339         if (currentMove >= forwardMostMove) {
5340           gameMode = PlayFromGameFile;
5341           ModeHighlight();
5342           SetTrainingModeOff();
5343           DisplayInformation(_("End of game"));
5344         }
5345       } else {
5346         DisplayError(_("Incorrect move"), 0);
5347       }
5348       return 1;
5349     }
5350
5351   /* Ok, now we know that the move is good, so we can kill
5352      the previous line in Analysis Mode */
5353   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5354     forwardMostMove = currentMove;
5355   }
5356
5357   /* If we need the chess program but it's dead, restart it */
5358   ResurrectChessProgram();
5359
5360   /* A user move restarts a paused game*/
5361   if (pausing)
5362     PauseEvent();
5363
5364   thinkOutput[0] = NULLCHAR;
5365
5366   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5367
5368   if (gameMode == BeginningOfGame) {
5369     if (appData.noChessProgram) {
5370       gameMode = EditGame;
5371       SetGameInfo();
5372     } else {
5373       char buf[MSG_SIZ];
5374       gameMode = MachinePlaysBlack;
5375       StartClocks();
5376       SetGameInfo();
5377       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5378       DisplayTitle(buf);
5379       if (first.sendName) {
5380         sprintf(buf, "name %s\n", gameInfo.white);
5381         SendToProgram(buf, &first);
5382       }
5383       StartClocks();
5384     }
5385     ModeHighlight();
5386   }
5387 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5388   /* Relay move to ICS or chess engine */
5389   if (appData.icsActive) {
5390     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5391         gameMode == IcsExamining) {
5392       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5393       ics_user_moved = 1;
5394     }
5395   } else {
5396     if (first.sendTime && (gameMode == BeginningOfGame ||
5397                            gameMode == MachinePlaysWhite ||
5398                            gameMode == MachinePlaysBlack)) {
5399       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5400     }
5401     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5402          // [HGM] book: if program might be playing, let it use book
5403         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5404         first.maybeThinking = TRUE;
5405     } else SendMoveToProgram(forwardMostMove-1, &first);
5406     if (currentMove == cmailOldMove + 1) {
5407       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5408     }
5409   }
5410
5411   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5412
5413   switch (gameMode) {
5414   case EditGame:
5415     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5416                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5417     case MT_NONE:
5418     case MT_CHECK:
5419       break;
5420     case MT_CHECKMATE:
5421     case MT_STAINMATE:
5422       if (WhiteOnMove(currentMove)) {
5423         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5424       } else {
5425         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5426       }
5427       break;
5428     case MT_STALEMATE:
5429       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5430       break;
5431     }
5432     break;
5433     
5434   case MachinePlaysBlack:
5435   case MachinePlaysWhite:
5436     /* disable certain menu options while machine is thinking */
5437     SetMachineThinkingEnables();
5438     break;
5439
5440   default:
5441     break;
5442   }
5443
5444   if(bookHit) { // [HGM] book: simulate book reply
5445         static char bookMove[MSG_SIZ]; // a bit generous?
5446
5447         programStats.nodes = programStats.depth = programStats.time = 
5448         programStats.score = programStats.got_only_move = 0;
5449         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5450
5451         strcpy(bookMove, "move ");
5452         strcat(bookMove, bookHit);
5453         HandleMachineMove(bookMove, &first);
5454   }
5455   return 1;
5456 }
5457
5458 void
5459 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5460      int fromX, fromY, toX, toY;
5461      int promoChar;
5462 {
5463     /* [HGM] This routine was added to allow calling of its two logical
5464        parts from other modules in the old way. Before, UserMoveEvent()
5465        automatically called FinishMove() if the move was OK, and returned
5466        otherwise. I separated the two, in order to make it possible to
5467        slip a promotion popup in between. But that it always needs two
5468        calls, to the first part, (now called UserMoveTest() ), and to
5469        FinishMove if the first part succeeded. Calls that do not need
5470        to do anything in between, can call this routine the old way. 
5471     */
5472     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5473 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5474     if(moveType == AmbiguousMove)
5475         DrawPosition(FALSE, boards[currentMove]);
5476     else if(moveType != ImpossibleMove && moveType != Comment)
5477         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5478 }
5479
5480 void LeftClick(ClickType clickType, int xPix, int yPix)
5481 {
5482     int x, y;
5483     Boolean saveAnimate;
5484     static int second = 0, promotionChoice = 0;
5485     char promoChoice = NULLCHAR;
5486
5487     if (clickType == Press) ErrorPopDown();
5488
5489     x = EventToSquare(xPix, BOARD_WIDTH);
5490     y = EventToSquare(yPix, BOARD_HEIGHT);
5491     if (!flipView && y >= 0) {
5492         y = BOARD_HEIGHT - 1 - y;
5493     }
5494     if (flipView && x >= 0) {
5495         x = BOARD_WIDTH - 1 - x;
5496     }
5497
5498     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5499         if(clickType == Release) return; // ignore upclick of click-click destination
5500         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5501         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5502         if(gameInfo.holdingsWidth && 
5503                 (WhiteOnMove(currentMove) 
5504                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5505                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5506             // click in right holdings, for determining promotion piece
5507             ChessSquare p = boards[currentMove][y][x];
5508             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5509             if(p != EmptySquare) {
5510                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5511                 fromX = fromY = -1;
5512                 return;
5513             }
5514         }
5515         DrawPosition(FALSE, boards[currentMove]);
5516         return;
5517     }
5518
5519     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5520     if(clickType == Press
5521             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5522               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5523               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5524         return;
5525
5526     if (fromX == -1) {
5527         if (clickType == Press) {
5528             /* First square */
5529             if (OKToStartUserMove(x, y)) {
5530                 fromX = x;
5531                 fromY = y;
5532                 second = 0;
5533                 DragPieceBegin(xPix, yPix);
5534                 if (appData.highlightDragging) {
5535                     SetHighlights(x, y, -1, -1);
5536                 }
5537             }
5538         }
5539         return;
5540     }
5541
5542     /* fromX != -1 */
5543     if (clickType == Press && gameMode != EditPosition) {
5544         ChessSquare fromP;
5545         ChessSquare toP;
5546         int frc;
5547
5548         // ignore off-board to clicks
5549         if(y < 0 || x < 0) return;
5550
5551         /* Check if clicking again on the same color piece */
5552         fromP = boards[currentMove][fromY][fromX];
5553         toP = boards[currentMove][y][x];
5554         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5555         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5556              WhitePawn <= toP && toP <= WhiteKing &&
5557              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5558              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5559             (BlackPawn <= fromP && fromP <= BlackKing && 
5560              BlackPawn <= toP && toP <= BlackKing &&
5561              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5562              !(fromP == BlackKing && toP == BlackRook && frc))) {
5563             /* Clicked again on same color piece -- changed his mind */
5564             second = (x == fromX && y == fromY);
5565             if (appData.highlightDragging) {
5566                 SetHighlights(x, y, -1, -1);
5567             } else {
5568                 ClearHighlights();
5569             }
5570             if (OKToStartUserMove(x, y)) {
5571                 fromX = x;
5572                 fromY = y;
5573                 DragPieceBegin(xPix, yPix);
5574             }
5575             return;
5576         }
5577         // ignore to-clicks in holdings
5578         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5579     }
5580
5581     if (clickType == Release && (x == fromX && y == fromY ||
5582         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5583
5584         // treat drags into holding as click on start square
5585         x = fromX; y = fromY;
5586
5587         DragPieceEnd(xPix, yPix);
5588         if (appData.animateDragging) {
5589             /* Undo animation damage if any */
5590             DrawPosition(FALSE, NULL);
5591         }
5592         if (second) {
5593             /* Second up/down in same square; just abort move */
5594             second = 0;
5595             fromX = fromY = -1;
5596             ClearHighlights();
5597             gotPremove = 0;
5598             ClearPremoveHighlights();
5599         } else {
5600             /* First upclick in same square; start click-click mode */
5601             SetHighlights(x, y, -1, -1);
5602         }
5603         return;
5604     }
5605
5606     /* we now have a different from- and to-square */
5607     /* Completed move */
5608     toX = x;
5609     toY = y;
5610     saveAnimate = appData.animate;
5611     if (clickType == Press) {
5612         /* Finish clickclick move */
5613         if (appData.animate || appData.highlightLastMove) {
5614             SetHighlights(fromX, fromY, toX, toY);
5615         } else {
5616             ClearHighlights();
5617         }
5618     } else {
5619         /* Finish drag move */
5620         if (appData.highlightLastMove) {
5621             SetHighlights(fromX, fromY, toX, toY);
5622         } else {
5623             ClearHighlights();
5624         }
5625         DragPieceEnd(xPix, yPix);
5626         /* Don't animate move and drag both */
5627         appData.animate = FALSE;
5628     }
5629     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5630         SetHighlights(fromX, fromY, toX, toY);
5631         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5632             // [HGM] super: promotion to captured piece selected from holdings
5633             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5634             promotionChoice = TRUE;
5635             // kludge follows to temporarily execute move on display, without promoting yet
5636             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5637             boards[currentMove][toY][toX] = p;
5638             DrawPosition(FALSE, boards[currentMove]);
5639             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5640             boards[currentMove][toY][toX] = q;
5641             DisplayMessage("Click in holdings to choose piece", "");
5642             return;
5643         }
5644         PromotionPopUp();
5645     } else {
5646         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5647         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5648         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5649         fromX = fromY = -1;
5650     }
5651     appData.animate = saveAnimate;
5652     if (appData.animate || appData.animateDragging) {
5653         /* Undo animation damage if needed */
5654         DrawPosition(FALSE, NULL);
5655     }
5656 }
5657
5658 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5659 {
5660 //    char * hint = lastHint;
5661     FrontEndProgramStats stats;
5662
5663     stats.which = cps == &first ? 0 : 1;
5664     stats.depth = cpstats->depth;
5665     stats.nodes = cpstats->nodes;
5666     stats.score = cpstats->score;
5667     stats.time = cpstats->time;
5668     stats.pv = cpstats->movelist;
5669     stats.hint = lastHint;
5670     stats.an_move_index = 0;
5671     stats.an_move_count = 0;
5672
5673     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5674         stats.hint = cpstats->move_name;
5675         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5676         stats.an_move_count = cpstats->nr_moves;
5677     }
5678
5679     SetProgramStats( &stats );
5680 }
5681
5682 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5683 {   // [HGM] book: this routine intercepts moves to simulate book replies
5684     char *bookHit = NULL;
5685
5686     //first determine if the incoming move brings opponent into his book
5687     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5688         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5689     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5690     if(bookHit != NULL && !cps->bookSuspend) {
5691         // make sure opponent is not going to reply after receiving move to book position
5692         SendToProgram("force\n", cps);
5693         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5694     }
5695     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5696     // now arrange restart after book miss
5697     if(bookHit) {
5698         // after a book hit we never send 'go', and the code after the call to this routine
5699         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5700         char buf[MSG_SIZ];
5701         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5702         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5703         SendToProgram(buf, cps);
5704         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5705     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5706         SendToProgram("go\n", cps);
5707         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5708     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5709         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5710             SendToProgram("go\n", cps); 
5711         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5712     }
5713     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5714 }
5715
5716 char *savedMessage;
5717 ChessProgramState *savedState;
5718 void DeferredBookMove(void)
5719 {
5720         if(savedState->lastPing != savedState->lastPong)
5721                     ScheduleDelayedEvent(DeferredBookMove, 10);
5722         else
5723         HandleMachineMove(savedMessage, savedState);
5724 }
5725
5726 void
5727 HandleMachineMove(message, cps)
5728      char *message;
5729      ChessProgramState *cps;
5730 {
5731     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5732     char realname[MSG_SIZ];
5733     int fromX, fromY, toX, toY;
5734     ChessMove moveType;
5735     char promoChar;
5736     char *p;
5737     int machineWhite;
5738     char *bookHit;
5739
5740 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5741     /*
5742      * Kludge to ignore BEL characters
5743      */
5744     while (*message == '\007') message++;
5745
5746     /*
5747      * [HGM] engine debug message: ignore lines starting with '#' character
5748      */
5749     if(cps->debug && *message == '#') return;
5750
5751     /*
5752      * Look for book output
5753      */
5754     if (cps == &first && bookRequested) {
5755         if (message[0] == '\t' || message[0] == ' ') {
5756             /* Part of the book output is here; append it */
5757             strcat(bookOutput, message);
5758             strcat(bookOutput, "  \n");
5759             return;
5760         } else if (bookOutput[0] != NULLCHAR) {
5761             /* All of book output has arrived; display it */
5762             char *p = bookOutput;
5763             while (*p != NULLCHAR) {
5764                 if (*p == '\t') *p = ' ';
5765                 p++;
5766             }
5767             DisplayInformation(bookOutput);
5768             bookRequested = FALSE;
5769             /* Fall through to parse the current output */
5770         }
5771     }
5772
5773     /*
5774      * Look for machine move.
5775      */
5776     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5777         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5778     {
5779         /* This method is only useful on engines that support ping */
5780         if (cps->lastPing != cps->lastPong) {
5781           if (gameMode == BeginningOfGame) {
5782             /* Extra move from before last new; ignore */
5783             if (appData.debugMode) {
5784                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5785             }
5786           } else {
5787             if (appData.debugMode) {
5788                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5789                         cps->which, gameMode);
5790             }
5791
5792             SendToProgram("undo\n", cps);
5793           }
5794           return;
5795         }
5796
5797         switch (gameMode) {
5798           case BeginningOfGame:
5799             /* Extra move from before last reset; ignore */
5800             if (appData.debugMode) {
5801                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5802             }
5803             return;
5804
5805           case EndOfGame:
5806           case IcsIdle:
5807           default:
5808             /* Extra move after we tried to stop.  The mode test is
5809                not a reliable way of detecting this problem, but it's
5810                the best we can do on engines that don't support ping.
5811             */
5812             if (appData.debugMode) {
5813                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5814                         cps->which, gameMode);
5815             }
5816             SendToProgram("undo\n", cps);
5817             return;
5818
5819           case MachinePlaysWhite:
5820           case IcsPlayingWhite:
5821             machineWhite = TRUE;
5822             break;
5823
5824           case MachinePlaysBlack:
5825           case IcsPlayingBlack:
5826             machineWhite = FALSE;
5827             break;
5828
5829           case TwoMachinesPlay:
5830             machineWhite = (cps->twoMachinesColor[0] == 'w');
5831             break;
5832         }
5833         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5834             if (appData.debugMode) {
5835                 fprintf(debugFP,
5836                         "Ignoring move out of turn by %s, gameMode %d"
5837                         ", forwardMost %d\n",
5838                         cps->which, gameMode, forwardMostMove);
5839             }
5840             return;
5841         }
5842
5843     if (appData.debugMode) { int f = forwardMostMove;
5844         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5845                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5846     }
5847         if(cps->alphaRank) AlphaRank(machineMove, 4);
5848         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5849                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5850             /* Machine move could not be parsed; ignore it. */
5851             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5852                     machineMove, cps->which);
5853             DisplayError(buf1, 0);
5854             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5855                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5856             if (gameMode == TwoMachinesPlay) {
5857               GameEnds(machineWhite ? BlackWins : WhiteWins,
5858                        buf1, GE_XBOARD);
5859             }
5860             return;
5861         }
5862
5863         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5864         /* So we have to redo legality test with true e.p. status here,  */
5865         /* to make sure an illegal e.p. capture does not slip through,   */
5866         /* to cause a forfeit on a justified illegal-move complaint      */
5867         /* of the opponent.                                              */
5868         if( gameMode==TwoMachinesPlay && appData.testLegality
5869             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5870                                                               ) {
5871            ChessMove moveType;
5872            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5873                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5874                              fromY, fromX, toY, toX, promoChar);
5875             if (appData.debugMode) {
5876                 int i;
5877                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5878                     castlingRights[forwardMostMove][i], castlingRank[i]);
5879                 fprintf(debugFP, "castling rights\n");
5880             }
5881             if(moveType == IllegalMove) {
5882                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5883                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5884                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5885                            buf1, GE_XBOARD);
5886                 return;
5887            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5888            /* [HGM] Kludge to handle engines that send FRC-style castling
5889               when they shouldn't (like TSCP-Gothic) */
5890            switch(moveType) {
5891              case WhiteASideCastleFR:
5892              case BlackASideCastleFR:
5893                toX+=2;
5894                currentMoveString[2]++;
5895                break;
5896              case WhiteHSideCastleFR:
5897              case BlackHSideCastleFR:
5898                toX--;
5899                currentMoveString[2]--;
5900                break;
5901              default: ; // nothing to do, but suppresses warning of pedantic compilers
5902            }
5903         }
5904         hintRequested = FALSE;
5905         lastHint[0] = NULLCHAR;
5906         bookRequested = FALSE;
5907         /* Program may be pondering now */
5908         cps->maybeThinking = TRUE;
5909         if (cps->sendTime == 2) cps->sendTime = 1;
5910         if (cps->offeredDraw) cps->offeredDraw--;
5911
5912 #if ZIPPY
5913         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5914             first.initDone) {
5915           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5916           ics_user_moved = 1;
5917           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5918                 char buf[3*MSG_SIZ];
5919
5920                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5921                         programStats.score / 100.,
5922                         programStats.depth,
5923                         programStats.time / 100.,
5924                         (unsigned int)programStats.nodes,
5925                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5926                         programStats.movelist);
5927                 SendToICS(buf);
5928 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5929           }
5930         }
5931 #endif
5932         /* currentMoveString is set as a side-effect of ParseOneMove */
5933         strcpy(machineMove, currentMoveString);
5934         strcat(machineMove, "\n");
5935         strcpy(moveList[forwardMostMove], machineMove);
5936
5937         /* [AS] Save move info and clear stats for next move */
5938         pvInfoList[ forwardMostMove ].score = programStats.score;
5939         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5940         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5941         ClearProgramStats();
5942         thinkOutput[0] = NULLCHAR;
5943         hiddenThinkOutputState = 0;
5944
5945         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5946
5947         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5948         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5949             int count = 0;
5950
5951             while( count < adjudicateLossPlies ) {
5952                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5953
5954                 if( count & 1 ) {
5955                     score = -score; /* Flip score for winning side */
5956                 }
5957
5958                 if( score > adjudicateLossThreshold ) {
5959                     break;
5960                 }
5961
5962                 count++;
5963             }
5964
5965             if( count >= adjudicateLossPlies ) {
5966                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5967
5968                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5969                     "Xboard adjudication", 
5970                     GE_XBOARD );
5971
5972                 return;
5973             }
5974         }
5975
5976         if( gameMode == TwoMachinesPlay ) {
5977           // [HGM] some adjudications useful with buggy engines
5978             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5979           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5980
5981
5982             if( appData.testLegality )
5983             {   /* [HGM] Some more adjudications for obstinate engines */
5984                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5985                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5986                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5987                 static int moveCount = 6;
5988                 ChessMove result;
5989                 char *reason = NULL;
5990
5991                 /* Count what is on board. */
5992                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5993                 {   ChessSquare p = boards[forwardMostMove][i][j];
5994                     int m=i;
5995
5996                     switch((int) p)
5997                     {   /* count B,N,R and other of each side */
5998                         case WhiteKing:
5999                         case BlackKing:
6000                              NrK++; break; // [HGM] atomic: count Kings
6001                         case WhiteKnight:
6002                              NrWN++; break;
6003                         case WhiteBishop:
6004                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6005                              bishopsColor |= 1 << ((i^j)&1);
6006                              NrWB++; break;
6007                         case BlackKnight:
6008                              NrBN++; break;
6009                         case BlackBishop:
6010                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6011                              bishopsColor |= 1 << ((i^j)&1);
6012                              NrBB++; break;
6013                         case WhiteRook:
6014                              NrWR++; break;
6015                         case BlackRook:
6016                              NrBR++; break;
6017                         case WhiteQueen:
6018                              NrWQ++; break;
6019                         case BlackQueen:
6020                              NrBQ++; break;
6021                         case EmptySquare: 
6022                              break;
6023                         case BlackPawn:
6024                              m = 7-i;
6025                         case WhitePawn:
6026                              PawnAdvance += m; NrPawns++;
6027                     }
6028                     NrPieces += (p != EmptySquare);
6029                     NrW += ((int)p < (int)BlackPawn);
6030                     if(gameInfo.variant == VariantXiangqi && 
6031                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6032                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6033                         NrW -= ((int)p < (int)BlackPawn);
6034                     }
6035                 }
6036
6037                 /* Some material-based adjudications that have to be made before stalemate test */
6038                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6039                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6040                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6041                      if(appData.checkMates) {
6042                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6043                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6044                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6045                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6046                          return;
6047                      }
6048                 }
6049
6050                 /* Bare King in Shatranj (loses) or Losers (wins) */
6051                 if( NrW == 1 || NrPieces - NrW == 1) {
6052                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6053                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6054                      if(appData.checkMates) {
6055                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6056                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6057                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6058                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6059                          return;
6060                      }
6061                   } else
6062                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6063                   {    /* bare King */
6064                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6065                         if(appData.checkMates) {
6066                             /* but only adjudicate if adjudication enabled */
6067                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6068                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6070                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6071                             return;
6072                         }
6073                   }
6074                 } else bare = 1;
6075
6076
6077             // don't wait for engine to announce game end if we can judge ourselves
6078             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6079                                        castlingRights[forwardMostMove]) ) {
6080               case MT_CHECK:
6081                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6082                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6083                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6084                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6085                             checkCnt++;
6086                         if(checkCnt >= 2) {
6087                             reason = "Xboard adjudication: 3rd check";
6088                             epStatus[forwardMostMove] = EP_CHECKMATE;
6089                             break;
6090                         }
6091                     }
6092                 }
6093               case MT_NONE:
6094               default:
6095                 break;
6096               case MT_STALEMATE:
6097               case MT_STAINMATE:
6098                 reason = "Xboard adjudication: Stalemate";
6099                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6100                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6101                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6102                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6103                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6104                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6105                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6106                                                                         EP_CHECKMATE : EP_WINS);
6107                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6108                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6109                 }
6110                 break;
6111               case MT_CHECKMATE:
6112                 reason = "Xboard adjudication: Checkmate";
6113                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6114                 break;
6115             }
6116
6117                 switch(i = epStatus[forwardMostMove]) {
6118                     case EP_STALEMATE:
6119                         result = GameIsDrawn; break;
6120                     case EP_CHECKMATE:
6121                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6122                     case EP_WINS:
6123                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6124                     default:
6125                         result = (ChessMove) 0;
6126                 }
6127                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6128                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6129                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6130                     GameEnds( result, reason, GE_XBOARD );
6131                     return;
6132                 }
6133
6134                 /* Next absolutely insufficient mating material. */
6135                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6136                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6137                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6138                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6139                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6140
6141                      /* always flag draws, for judging claims */
6142                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6143
6144                      if(appData.materialDraws) {
6145                          /* but only adjudicate them if adjudication enabled */
6146                          SendToProgram("force\n", cps->other); // suppress reply
6147                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6148                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6149                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6150                          return;
6151                      }
6152                 }
6153
6154                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6155                 if(NrPieces == 4 && 
6156                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6157                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6158                    || NrWN==2 || NrBN==2     /* KNNK */
6159                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6160                   ) ) {
6161                      if(--moveCount < 0 && appData.trivialDraws)
6162                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6163                           SendToProgram("force\n", cps->other); // suppress reply
6164                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6165                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6166                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6167                           return;
6168                      }
6169                 } else moveCount = 6;
6170             }
6171           }
6172           
6173           if (appData.debugMode) { int i;
6174             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6175                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6176                     appData.drawRepeats);
6177             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6178               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6179             
6180           }
6181
6182                 /* Check for rep-draws */
6183                 count = 0;
6184                 for(k = forwardMostMove-2;
6185                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6186                         epStatus[k] < EP_UNKNOWN &&
6187                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6188                     k-=2)
6189                 {   int rights=0;
6190                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6191                         /* compare castling rights */
6192                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6193                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6194                                 rights++; /* King lost rights, while rook still had them */
6195                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6196                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6197                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6198                                    rights++; /* but at least one rook lost them */
6199                         }
6200                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6201                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6202                                 rights++; 
6203                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6204                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6205                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6206                                    rights++;
6207                         }
6208                         if( rights == 0 && ++count > appData.drawRepeats-2
6209                             && appData.drawRepeats > 1) {
6210                              /* adjudicate after user-specified nr of repeats */
6211                              SendToProgram("force\n", cps->other); // suppress reply
6212                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6213                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6214                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6215                                 // [HGM] xiangqi: check for forbidden perpetuals
6216                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6217                                 for(m=forwardMostMove; m>k; m-=2) {
6218                                     if(MateTest(boards[m], PosFlags(m), 
6219                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6220                                         ourPerpetual = 0; // the current mover did not always check
6221                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6222                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6223                                         hisPerpetual = 0; // the opponent did not always check
6224                                 }
6225                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6226                                                                         ourPerpetual, hisPerpetual);
6227                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6228                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6229                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6230                                     return;
6231                                 }
6232                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6233                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6234                                 // Now check for perpetual chases
6235                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6236                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6237                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6238                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6239                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6240                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6241                                         return;
6242                                     }
6243                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6244                                         break; // Abort repetition-checking loop.
6245                                 }
6246                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6247                              }
6248                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6249                              return;
6250                         }
6251                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6252                              epStatus[forwardMostMove] = EP_REP_DRAW;
6253                     }
6254                 }
6255
6256                 /* Now we test for 50-move draws. Determine ply count */
6257                 count = forwardMostMove;
6258                 /* look for last irreversble move */
6259                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6260                     count--;
6261                 /* if we hit starting position, add initial plies */
6262                 if( count == backwardMostMove )
6263                     count -= initialRulePlies;
6264                 count = forwardMostMove - count; 
6265                 if( count >= 100)
6266                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6267                          /* this is used to judge if draw claims are legal */
6268                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6269                          SendToProgram("force\n", cps->other); // suppress reply
6270                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6271                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6272                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6273                          return;
6274                 }
6275
6276                 /* if draw offer is pending, treat it as a draw claim
6277                  * when draw condition present, to allow engines a way to
6278                  * claim draws before making their move to avoid a race
6279                  * condition occurring after their move
6280                  */
6281                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6282                          char *p = NULL;
6283                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6284                              p = "Draw claim: 50-move rule";
6285                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6286                              p = "Draw claim: 3-fold repetition";
6287                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6288                              p = "Draw claim: insufficient mating material";
6289                          if( p != NULL ) {
6290                              SendToProgram("force\n", cps->other); // suppress reply
6291                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6292                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6293                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6294                              return;
6295                          }
6296                 }
6297
6298
6299                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6300                     SendToProgram("force\n", cps->other); // suppress reply
6301                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6302                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6303
6304                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6305
6306                     return;
6307                 }
6308         }
6309
6310         bookHit = NULL;
6311         if (gameMode == TwoMachinesPlay) {
6312             /* [HGM] relaying draw offers moved to after reception of move */
6313             /* and interpreting offer as claim if it brings draw condition */
6314             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6315                 SendToProgram("draw\n", cps->other);
6316             }
6317             if (cps->other->sendTime) {
6318                 SendTimeRemaining(cps->other,
6319                                   cps->other->twoMachinesColor[0] == 'w');
6320             }
6321             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6322             if (firstMove && !bookHit) {
6323                 firstMove = FALSE;
6324                 if (cps->other->useColors) {
6325                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6326                 }
6327                 SendToProgram("go\n", cps->other);
6328             }
6329             cps->other->maybeThinking = TRUE;
6330         }
6331
6332         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6333         
6334         if (!pausing && appData.ringBellAfterMoves) {
6335             RingBell();
6336         }
6337
6338         /* 
6339          * Reenable menu items that were disabled while
6340          * machine was thinking
6341          */
6342         if (gameMode != TwoMachinesPlay)
6343             SetUserThinkingEnables();
6344
6345         // [HGM] book: after book hit opponent has received move and is now in force mode
6346         // force the book reply into it, and then fake that it outputted this move by jumping
6347         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6348         if(bookHit) {
6349                 static char bookMove[MSG_SIZ]; // a bit generous?
6350
6351                 strcpy(bookMove, "move ");
6352                 strcat(bookMove, bookHit);
6353                 message = bookMove;
6354                 cps = cps->other;
6355                 programStats.nodes = programStats.depth = programStats.time = 
6356                 programStats.score = programStats.got_only_move = 0;
6357                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6358
6359                 if(cps->lastPing != cps->lastPong) {
6360                     savedMessage = message; // args for deferred call
6361                     savedState = cps;
6362                     ScheduleDelayedEvent(DeferredBookMove, 10);
6363                     return;
6364                 }
6365                 goto FakeBookMove;
6366         }
6367
6368         return;
6369     }
6370
6371     /* Set special modes for chess engines.  Later something general
6372      *  could be added here; for now there is just one kludge feature,
6373      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6374      *  when "xboard" is given as an interactive command.
6375      */
6376     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6377         cps->useSigint = FALSE;
6378         cps->useSigterm = FALSE;
6379     }
6380     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6381       ParseFeatures(message+8, cps);
6382       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6383     }
6384
6385     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6386      * want this, I was asked to put it in, and obliged.
6387      */
6388     if (!strncmp(message, "setboard ", 9)) {
6389         Board initial_position; int i;
6390
6391         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6392
6393         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6394             DisplayError(_("Bad FEN received from engine"), 0);
6395             return ;
6396         } else {
6397            Reset(FALSE, FALSE);
6398            CopyBoard(boards[0], initial_position);
6399            initialRulePlies = FENrulePlies;
6400            epStatus[0] = FENepStatus;
6401            for( i=0; i<nrCastlingRights; i++ )
6402                 castlingRights[0][i] = FENcastlingRights[i];
6403            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6404            else gameMode = MachinePlaysBlack;                 
6405            DrawPosition(FALSE, boards[currentMove]);
6406         }
6407         return;
6408     }
6409
6410     /*
6411      * Look for communication commands
6412      */
6413     if (!strncmp(message, "telluser ", 9)) {
6414         DisplayNote(message + 9);
6415         return;
6416     }
6417     if (!strncmp(message, "tellusererror ", 14)) {
6418         DisplayError(message + 14, 0);
6419         return;
6420     }
6421     if (!strncmp(message, "tellopponent ", 13)) {
6422       if (appData.icsActive) {
6423         if (loggedOn) {
6424           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6425           SendToICS(buf1);
6426         }
6427       } else {
6428         DisplayNote(message + 13);
6429       }
6430       return;
6431     }
6432     if (!strncmp(message, "tellothers ", 11)) {
6433       if (appData.icsActive) {
6434         if (loggedOn) {
6435           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6436           SendToICS(buf1);
6437         }
6438       }
6439       return;
6440     }
6441     if (!strncmp(message, "tellall ", 8)) {
6442       if (appData.icsActive) {
6443         if (loggedOn) {
6444           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6445           SendToICS(buf1);
6446         }
6447       } else {
6448         DisplayNote(message + 8);
6449       }
6450       return;
6451     }
6452     if (strncmp(message, "warning", 7) == 0) {
6453         /* Undocumented feature, use tellusererror in new code */
6454         DisplayError(message, 0);
6455         return;
6456     }
6457     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6458         strcpy(realname, cps->tidy);
6459         strcat(realname, " query");
6460         AskQuestion(realname, buf2, buf1, cps->pr);
6461         return;
6462     }
6463     /* Commands from the engine directly to ICS.  We don't allow these to be 
6464      *  sent until we are logged on. Crafty kibitzes have been known to 
6465      *  interfere with the login process.
6466      */
6467     if (loggedOn) {
6468         if (!strncmp(message, "tellics ", 8)) {
6469             SendToICS(message + 8);
6470             SendToICS("\n");
6471             return;
6472         }
6473         if (!strncmp(message, "tellicsnoalias ", 15)) {
6474             SendToICS(ics_prefix);
6475             SendToICS(message + 15);
6476             SendToICS("\n");
6477             return;
6478         }
6479         /* The following are for backward compatibility only */
6480         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6481             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6482             SendToICS(ics_prefix);
6483             SendToICS(message);
6484             SendToICS("\n");
6485             return;
6486         }
6487     }
6488     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6489         return;
6490     }
6491     /*
6492      * If the move is illegal, cancel it and redraw the board.
6493      * Also deal with other error cases.  Matching is rather loose
6494      * here to accommodate engines written before the spec.
6495      */
6496     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6497         strncmp(message, "Error", 5) == 0) {
6498         if (StrStr(message, "name") || 
6499             StrStr(message, "rating") || StrStr(message, "?") ||
6500             StrStr(message, "result") || StrStr(message, "board") ||
6501             StrStr(message, "bk") || StrStr(message, "computer") ||
6502             StrStr(message, "variant") || StrStr(message, "hint") ||
6503             StrStr(message, "random") || StrStr(message, "depth") ||
6504             StrStr(message, "accepted")) {
6505             return;
6506         }
6507         if (StrStr(message, "protover")) {
6508           /* Program is responding to input, so it's apparently done
6509              initializing, and this error message indicates it is
6510              protocol version 1.  So we don't need to wait any longer
6511              for it to initialize and send feature commands. */
6512           FeatureDone(cps, 1);
6513           cps->protocolVersion = 1;
6514           return;
6515         }
6516         cps->maybeThinking = FALSE;
6517
6518         if (StrStr(message, "draw")) {
6519             /* Program doesn't have "draw" command */
6520             cps->sendDrawOffers = 0;
6521             return;
6522         }
6523         if (cps->sendTime != 1 &&
6524             (StrStr(message, "time") || StrStr(message, "otim"))) {
6525           /* Program apparently doesn't have "time" or "otim" command */
6526           cps->sendTime = 0;
6527           return;
6528         }
6529         if (StrStr(message, "analyze")) {
6530             cps->analysisSupport = FALSE;
6531             cps->analyzing = FALSE;
6532             Reset(FALSE, TRUE);
6533             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6534             DisplayError(buf2, 0);
6535             return;
6536         }
6537         if (StrStr(message, "(no matching move)st")) {
6538           /* Special kludge for GNU Chess 4 only */
6539           cps->stKludge = TRUE;
6540           SendTimeControl(cps, movesPerSession, timeControl,
6541                           timeIncrement, appData.searchDepth,
6542                           searchTime);
6543           return;
6544         }
6545         if (StrStr(message, "(no matching move)sd")) {
6546           /* Special kludge for GNU Chess 4 only */
6547           cps->sdKludge = TRUE;
6548           SendTimeControl(cps, movesPerSession, timeControl,
6549                           timeIncrement, appData.searchDepth,
6550                           searchTime);
6551           return;
6552         }
6553         if (!StrStr(message, "llegal")) {
6554             return;
6555         }
6556         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6557             gameMode == IcsIdle) return;
6558         if (forwardMostMove <= backwardMostMove) return;
6559         if (pausing) PauseEvent();
6560       if(appData.forceIllegal) {
6561             // [HGM] illegal: machine refused move; force position after move into it
6562           SendToProgram("force\n", cps);
6563           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6564                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6565                 // when black is to move, while there might be nothing on a2 or black
6566                 // might already have the move. So send the board as if white has the move.
6567                 // But first we must change the stm of the engine, as it refused the last move
6568                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6569                 if(WhiteOnMove(forwardMostMove)) {
6570                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6571                     SendBoard(cps, forwardMostMove); // kludgeless board
6572                 } else {
6573                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6574                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6575                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6576                 }
6577           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6578             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6579                  gameMode == TwoMachinesPlay)
6580               SendToProgram("go\n", cps);
6581             return;
6582       } else
6583         if (gameMode == PlayFromGameFile) {
6584             /* Stop reading this game file */
6585             gameMode = EditGame;
6586             ModeHighlight();
6587         }
6588         currentMove = --forwardMostMove;
6589         DisplayMove(currentMove-1); /* before DisplayMoveError */
6590         SwitchClocks();
6591         DisplayBothClocks();
6592         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6593                 parseList[currentMove], cps->which);
6594         DisplayMoveError(buf1);
6595         DrawPosition(FALSE, boards[currentMove]);
6596
6597         /* [HGM] illegal-move claim should forfeit game when Xboard */
6598         /* only passes fully legal moves                            */
6599         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6600             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6601                                 "False illegal-move claim", GE_XBOARD );
6602         }
6603         return;
6604     }
6605     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6606         /* Program has a broken "time" command that
6607            outputs a string not ending in newline.
6608            Don't use it. */
6609         cps->sendTime = 0;
6610     }
6611     
6612     /*
6613      * If chess program startup fails, exit with an error message.
6614      * Attempts to recover here are futile.
6615      */
6616     if ((StrStr(message, "unknown host") != NULL)
6617         || (StrStr(message, "No remote directory") != NULL)
6618         || (StrStr(message, "not found") != NULL)
6619         || (StrStr(message, "No such file") != NULL)
6620         || (StrStr(message, "can't alloc") != NULL)
6621         || (StrStr(message, "Permission denied") != NULL)) {
6622
6623         cps->maybeThinking = FALSE;
6624         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6625                 cps->which, cps->program, cps->host, message);
6626         RemoveInputSource(cps->isr);
6627         DisplayFatalError(buf1, 0, 1);
6628         return;
6629     }
6630     
6631     /* 
6632      * Look for hint output
6633      */
6634     if (sscanf(message, "Hint: %s", buf1) == 1) {
6635         if (cps == &first && hintRequested) {
6636             hintRequested = FALSE;
6637             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6638                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6639                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6640                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6641                                     fromY, fromX, toY, toX, promoChar, buf1);
6642                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6643                 DisplayInformation(buf2);
6644             } else {
6645                 /* Hint move could not be parsed!? */
6646               snprintf(buf2, sizeof(buf2),
6647                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6648                         buf1, cps->which);
6649                 DisplayError(buf2, 0);
6650             }
6651         } else {
6652             strcpy(lastHint, buf1);
6653         }
6654         return;
6655     }
6656
6657     /*
6658      * Ignore other messages if game is not in progress
6659      */
6660     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6661         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6662
6663     /*
6664      * look for win, lose, draw, or draw offer
6665      */
6666     if (strncmp(message, "1-0", 3) == 0) {
6667         char *p, *q, *r = "";
6668         p = strchr(message, '{');
6669         if (p) {
6670             q = strchr(p, '}');
6671             if (q) {
6672                 *q = NULLCHAR;
6673                 r = p + 1;
6674             }
6675         }
6676         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6677         return;
6678     } else if (strncmp(message, "0-1", 3) == 0) {
6679         char *p, *q, *r = "";
6680         p = strchr(message, '{');
6681         if (p) {
6682             q = strchr(p, '}');
6683             if (q) {
6684                 *q = NULLCHAR;
6685                 r = p + 1;
6686             }
6687         }
6688         /* Kludge for Arasan 4.1 bug */
6689         if (strcmp(r, "Black resigns") == 0) {
6690             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6691             return;
6692         }
6693         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6694         return;
6695     } else if (strncmp(message, "1/2", 3) == 0) {
6696         char *p, *q, *r = "";
6697         p = strchr(message, '{');
6698         if (p) {
6699             q = strchr(p, '}');
6700             if (q) {
6701                 *q = NULLCHAR;
6702                 r = p + 1;
6703             }
6704         }
6705             
6706         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6707         return;
6708
6709     } else if (strncmp(message, "White resign", 12) == 0) {
6710         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6711         return;
6712     } else if (strncmp(message, "Black resign", 12) == 0) {
6713         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6714         return;
6715     } else if (strncmp(message, "White matches", 13) == 0 ||
6716                strncmp(message, "Black matches", 13) == 0   ) {
6717         /* [HGM] ignore GNUShogi noises */
6718         return;
6719     } else if (strncmp(message, "White", 5) == 0 &&
6720                message[5] != '(' &&
6721                StrStr(message, "Black") == NULL) {
6722         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6723         return;
6724     } else if (strncmp(message, "Black", 5) == 0 &&
6725                message[5] != '(') {
6726         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6727         return;
6728     } else if (strcmp(message, "resign") == 0 ||
6729                strcmp(message, "computer resigns") == 0) {
6730         switch (gameMode) {
6731           case MachinePlaysBlack:
6732           case IcsPlayingBlack:
6733             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6734             break;
6735           case MachinePlaysWhite:
6736           case IcsPlayingWhite:
6737             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6738             break;
6739           case TwoMachinesPlay:
6740             if (cps->twoMachinesColor[0] == 'w')
6741               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6742             else
6743               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6744             break;
6745           default:
6746             /* can't happen */
6747             break;
6748         }
6749         return;
6750     } else if (strncmp(message, "opponent mates", 14) == 0) {
6751         switch (gameMode) {
6752           case MachinePlaysBlack:
6753           case IcsPlayingBlack:
6754             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6755             break;
6756           case MachinePlaysWhite:
6757           case IcsPlayingWhite:
6758             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6759             break;
6760           case TwoMachinesPlay:
6761             if (cps->twoMachinesColor[0] == 'w')
6762               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6763             else
6764               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6765             break;
6766           default:
6767             /* can't happen */
6768             break;
6769         }
6770         return;
6771     } else if (strncmp(message, "computer mates", 14) == 0) {
6772         switch (gameMode) {
6773           case MachinePlaysBlack:
6774           case IcsPlayingBlack:
6775             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6776             break;
6777           case MachinePlaysWhite:
6778           case IcsPlayingWhite:
6779             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6780             break;
6781           case TwoMachinesPlay:
6782             if (cps->twoMachinesColor[0] == 'w')
6783               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6784             else
6785               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6786             break;
6787           default:
6788             /* can't happen */
6789             break;
6790         }
6791         return;
6792     } else if (strncmp(message, "checkmate", 9) == 0) {
6793         if (WhiteOnMove(forwardMostMove)) {
6794             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6795         } else {
6796             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6797         }
6798         return;
6799     } else if (strstr(message, "Draw") != NULL ||
6800                strstr(message, "game is a draw") != NULL) {
6801         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6802         return;
6803     } else if (strstr(message, "offer") != NULL &&
6804                strstr(message, "draw") != NULL) {
6805 #if ZIPPY
6806         if (appData.zippyPlay && first.initDone) {
6807             /* Relay offer to ICS */
6808             SendToICS(ics_prefix);
6809             SendToICS("draw\n");
6810         }
6811 #endif
6812         cps->offeredDraw = 2; /* valid until this engine moves twice */
6813         if (gameMode == TwoMachinesPlay) {
6814             if (cps->other->offeredDraw) {
6815                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6816             /* [HGM] in two-machine mode we delay relaying draw offer      */
6817             /* until after we also have move, to see if it is really claim */
6818             }
6819         } else if (gameMode == MachinePlaysWhite ||
6820                    gameMode == MachinePlaysBlack) {
6821           if (userOfferedDraw) {
6822             DisplayInformation(_("Machine accepts your draw offer"));
6823             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6824           } else {
6825             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6826           }
6827         }
6828     }
6829
6830     
6831     /*
6832      * Look for thinking output
6833      */
6834     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6835           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6836                                 ) {
6837         int plylev, mvleft, mvtot, curscore, time;
6838         char mvname[MOVE_LEN];
6839         u64 nodes; // [DM]
6840         char plyext;
6841         int ignore = FALSE;
6842         int prefixHint = FALSE;
6843         mvname[0] = NULLCHAR;
6844
6845         switch (gameMode) {
6846           case MachinePlaysBlack:
6847           case IcsPlayingBlack:
6848             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6849             break;
6850           case MachinePlaysWhite:
6851           case IcsPlayingWhite:
6852             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6853             break;
6854           case AnalyzeMode:
6855           case AnalyzeFile:
6856             break;
6857           case IcsObserving: /* [DM] icsEngineAnalyze */
6858             if (!appData.icsEngineAnalyze) ignore = TRUE;
6859             break;
6860           case TwoMachinesPlay:
6861             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6862                 ignore = TRUE;
6863             }
6864             break;
6865           default:
6866             ignore = TRUE;
6867             break;
6868         }
6869
6870         if (!ignore) {
6871             buf1[0] = NULLCHAR;
6872             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6873                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6874
6875                 if (plyext != ' ' && plyext != '\t') {
6876                     time *= 100;
6877                 }
6878
6879                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6880                 if( cps->scoreIsAbsolute && 
6881                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6882                 {
6883                     curscore = -curscore;
6884                 }
6885
6886
6887                 programStats.depth = plylev;
6888                 programStats.nodes = nodes;
6889                 programStats.time = time;
6890                 programStats.score = curscore;
6891                 programStats.got_only_move = 0;
6892
6893                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6894                         int ticklen;
6895
6896                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6897                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6898                         if(WhiteOnMove(forwardMostMove)) 
6899                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6900                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6901                 }
6902
6903                 /* Buffer overflow protection */
6904                 if (buf1[0] != NULLCHAR) {
6905                     if (strlen(buf1) >= sizeof(programStats.movelist)
6906                         && appData.debugMode) {
6907                         fprintf(debugFP,
6908                                 "PV is too long; using the first %d bytes.\n",
6909                                 sizeof(programStats.movelist) - 1);
6910                     }
6911
6912                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6913                 } else {
6914                     sprintf(programStats.movelist, " no PV\n");
6915                 }
6916
6917                 if (programStats.seen_stat) {
6918                     programStats.ok_to_send = 1;
6919                 }
6920
6921                 if (strchr(programStats.movelist, '(') != NULL) {
6922                     programStats.line_is_book = 1;
6923                     programStats.nr_moves = 0;
6924                     programStats.moves_left = 0;
6925                 } else {
6926                     programStats.line_is_book = 0;
6927                 }
6928
6929                 SendProgramStatsToFrontend( cps, &programStats );
6930
6931                 /* 
6932                     [AS] Protect the thinkOutput buffer from overflow... this
6933                     is only useful if buf1 hasn't overflowed first!
6934                 */
6935                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6936                         plylev, 
6937                         (gameMode == TwoMachinesPlay ?
6938                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6939                         ((double) curscore) / 100.0,
6940                         prefixHint ? lastHint : "",
6941                         prefixHint ? " " : "" );
6942
6943                 if( buf1[0] != NULLCHAR ) {
6944                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6945
6946                     if( strlen(buf1) > max_len ) {
6947                         if( appData.debugMode) {
6948                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6949                         }
6950                         buf1[max_len+1] = '\0';
6951                     }
6952
6953                     strcat( thinkOutput, buf1 );
6954                 }
6955
6956                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6957                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6958                     DisplayMove(currentMove - 1);
6959                 }
6960                 return;
6961
6962             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6963                 /* crafty (9.25+) says "(only move) <move>"
6964                  * if there is only 1 legal move
6965                  */
6966                 sscanf(p, "(only move) %s", buf1);
6967                 sprintf(thinkOutput, "%s (only move)", buf1);
6968                 sprintf(programStats.movelist, "%s (only move)", buf1);
6969                 programStats.depth = 1;
6970                 programStats.nr_moves = 1;
6971                 programStats.moves_left = 1;
6972                 programStats.nodes = 1;
6973                 programStats.time = 1;
6974                 programStats.got_only_move = 1;
6975
6976                 /* Not really, but we also use this member to
6977                    mean "line isn't going to change" (Crafty
6978                    isn't searching, so stats won't change) */
6979                 programStats.line_is_book = 1;
6980
6981                 SendProgramStatsToFrontend( cps, &programStats );
6982                 
6983                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6984                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6985                     DisplayMove(currentMove - 1);
6986                 }
6987                 return;
6988             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6989                               &time, &nodes, &plylev, &mvleft,
6990                               &mvtot, mvname) >= 5) {
6991                 /* The stat01: line is from Crafty (9.29+) in response
6992                    to the "." command */
6993                 programStats.seen_stat = 1;
6994                 cps->maybeThinking = TRUE;
6995
6996                 if (programStats.got_only_move || !appData.periodicUpdates)
6997                   return;
6998
6999                 programStats.depth = plylev;
7000                 programStats.time = time;
7001                 programStats.nodes = nodes;
7002                 programStats.moves_left = mvleft;
7003                 programStats.nr_moves = mvtot;
7004                 strcpy(programStats.move_name, mvname);
7005                 programStats.ok_to_send = 1;
7006                 programStats.movelist[0] = '\0';
7007
7008                 SendProgramStatsToFrontend( cps, &programStats );
7009
7010                 return;
7011
7012             } else if (strncmp(message,"++",2) == 0) {
7013                 /* Crafty 9.29+ outputs this */
7014                 programStats.got_fail = 2;
7015                 return;
7016
7017             } else if (strncmp(message,"--",2) == 0) {
7018                 /* Crafty 9.29+ outputs this */
7019                 programStats.got_fail = 1;
7020                 return;
7021
7022             } else if (thinkOutput[0] != NULLCHAR &&
7023                        strncmp(message, "    ", 4) == 0) {
7024                 unsigned message_len;
7025
7026                 p = message;
7027                 while (*p && *p == ' ') p++;
7028
7029                 message_len = strlen( p );
7030
7031                 /* [AS] Avoid buffer overflow */
7032                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7033                     strcat(thinkOutput, " ");
7034                     strcat(thinkOutput, p);
7035                 }
7036
7037                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7038                     strcat(programStats.movelist, " ");
7039                     strcat(programStats.movelist, p);
7040                 }
7041
7042                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7043                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7044                     DisplayMove(currentMove - 1);
7045                 }
7046                 return;
7047             }
7048         }
7049         else {
7050             buf1[0] = NULLCHAR;
7051
7052             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7053                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7054             {
7055                 ChessProgramStats cpstats;
7056
7057                 if (plyext != ' ' && plyext != '\t') {
7058                     time *= 100;
7059                 }
7060
7061                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7062                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7063                     curscore = -curscore;
7064                 }
7065
7066                 cpstats.depth = plylev;
7067                 cpstats.nodes = nodes;
7068                 cpstats.time = time;
7069                 cpstats.score = curscore;
7070                 cpstats.got_only_move = 0;
7071                 cpstats.movelist[0] = '\0';
7072
7073                 if (buf1[0] != NULLCHAR) {
7074                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7075                 }
7076
7077                 cpstats.ok_to_send = 0;
7078                 cpstats.line_is_book = 0;
7079                 cpstats.nr_moves = 0;
7080                 cpstats.moves_left = 0;
7081
7082                 SendProgramStatsToFrontend( cps, &cpstats );
7083             }
7084         }
7085     }
7086 }
7087
7088
7089 /* Parse a game score from the character string "game", and
7090    record it as the history of the current game.  The game
7091    score is NOT assumed to start from the standard position. 
7092    The display is not updated in any way.
7093    */
7094 void
7095 ParseGameHistory(game)
7096      char *game;
7097 {
7098     ChessMove moveType;
7099     int fromX, fromY, toX, toY, boardIndex;
7100     char promoChar;
7101     char *p, *q;
7102     char buf[MSG_SIZ];
7103
7104     if (appData.debugMode)
7105       fprintf(debugFP, "Parsing game history: %s\n", game);
7106
7107     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7108     gameInfo.site = StrSave(appData.icsHost);
7109     gameInfo.date = PGNDate();
7110     gameInfo.round = StrSave("-");
7111
7112     /* Parse out names of players */
7113     while (*game == ' ') game++;
7114     p = buf;
7115     while (*game != ' ') *p++ = *game++;
7116     *p = NULLCHAR;
7117     gameInfo.white = StrSave(buf);
7118     while (*game == ' ') game++;
7119     p = buf;
7120     while (*game != ' ' && *game != '\n') *p++ = *game++;
7121     *p = NULLCHAR;
7122     gameInfo.black = StrSave(buf);
7123
7124     /* Parse moves */
7125     boardIndex = blackPlaysFirst ? 1 : 0;
7126     yynewstr(game);
7127     for (;;) {
7128         yyboardindex = boardIndex;
7129         moveType = (ChessMove) yylex();
7130         switch (moveType) {
7131           case IllegalMove:             /* maybe suicide chess, etc. */
7132   if (appData.debugMode) {
7133     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7134     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7135     setbuf(debugFP, NULL);
7136   }
7137           case WhitePromotionChancellor:
7138           case BlackPromotionChancellor:
7139           case WhitePromotionArchbishop:
7140           case BlackPromotionArchbishop:
7141           case WhitePromotionQueen:
7142           case BlackPromotionQueen:
7143           case WhitePromotionRook:
7144           case BlackPromotionRook:
7145           case WhitePromotionBishop:
7146           case BlackPromotionBishop:
7147           case WhitePromotionKnight:
7148           case BlackPromotionKnight:
7149           case WhitePromotionKing:
7150           case BlackPromotionKing:
7151           case NormalMove:
7152           case WhiteCapturesEnPassant:
7153           case BlackCapturesEnPassant:
7154           case WhiteKingSideCastle:
7155           case WhiteQueenSideCastle:
7156           case BlackKingSideCastle:
7157           case BlackQueenSideCastle:
7158           case WhiteKingSideCastleWild:
7159           case WhiteQueenSideCastleWild:
7160           case BlackKingSideCastleWild:
7161           case BlackQueenSideCastleWild:
7162           /* PUSH Fabien */
7163           case WhiteHSideCastleFR:
7164           case WhiteASideCastleFR:
7165           case BlackHSideCastleFR:
7166           case BlackASideCastleFR:
7167           /* POP Fabien */
7168             fromX = currentMoveString[0] - AAA;
7169             fromY = currentMoveString[1] - ONE;
7170             toX = currentMoveString[2] - AAA;
7171             toY = currentMoveString[3] - ONE;
7172             promoChar = currentMoveString[4];
7173             break;
7174           case WhiteDrop:
7175           case BlackDrop:
7176             fromX = moveType == WhiteDrop ?
7177               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7178             (int) CharToPiece(ToLower(currentMoveString[0]));
7179             fromY = DROP_RANK;
7180             toX = currentMoveString[2] - AAA;
7181             toY = currentMoveString[3] - ONE;
7182             promoChar = NULLCHAR;
7183             break;
7184           case AmbiguousMove:
7185             /* bug? */
7186             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7187   if (appData.debugMode) {
7188     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7189     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7190     setbuf(debugFP, NULL);
7191   }
7192             DisplayError(buf, 0);
7193             return;
7194           case ImpossibleMove:
7195             /* bug? */
7196             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7197   if (appData.debugMode) {
7198     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7199     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7200     setbuf(debugFP, NULL);
7201   }
7202             DisplayError(buf, 0);
7203             return;
7204           case (ChessMove) 0:   /* end of file */
7205             if (boardIndex < backwardMostMove) {
7206                 /* Oops, gap.  How did that happen? */
7207                 DisplayError(_("Gap in move list"), 0);
7208                 return;
7209             }
7210             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7211             if (boardIndex > forwardMostMove) {
7212                 forwardMostMove = boardIndex;
7213             }
7214             return;
7215           case ElapsedTime:
7216             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7217                 strcat(parseList[boardIndex-1], " ");
7218                 strcat(parseList[boardIndex-1], yy_text);
7219             }
7220             continue;
7221           case Comment:
7222           case PGNTag:
7223           case NAG:
7224           default:
7225             /* ignore */
7226             continue;
7227           case WhiteWins:
7228           case BlackWins:
7229           case GameIsDrawn:
7230           case GameUnfinished:
7231             if (gameMode == IcsExamining) {
7232                 if (boardIndex < backwardMostMove) {
7233                     /* Oops, gap.  How did that happen? */
7234                     return;
7235                 }
7236                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7237                 return;
7238             }
7239             gameInfo.result = moveType;
7240             p = strchr(yy_text, '{');
7241             if (p == NULL) p = strchr(yy_text, '(');
7242             if (p == NULL) {
7243                 p = yy_text;
7244                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7245             } else {
7246                 q = strchr(p, *p == '{' ? '}' : ')');
7247                 if (q != NULL) *q = NULLCHAR;
7248                 p++;
7249             }
7250             gameInfo.resultDetails = StrSave(p);
7251             continue;
7252         }
7253         if (boardIndex >= forwardMostMove &&
7254             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7255             backwardMostMove = blackPlaysFirst ? 1 : 0;
7256             return;
7257         }
7258         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7259                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7260                                  parseList[boardIndex]);
7261         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7262         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7263         /* currentMoveString is set as a side-effect of yylex */
7264         strcpy(moveList[boardIndex], currentMoveString);
7265         strcat(moveList[boardIndex], "\n");
7266         boardIndex++;
7267         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7268                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7269         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7270                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7271           case MT_NONE:
7272           case MT_STALEMATE:
7273           default:
7274             break;
7275           case MT_CHECK:
7276             if(gameInfo.variant != VariantShogi)
7277                 strcat(parseList[boardIndex - 1], "+");
7278             break;
7279           case MT_CHECKMATE:
7280           case MT_STAINMATE:
7281             strcat(parseList[boardIndex - 1], "#");
7282             break;
7283         }
7284     }
7285 }
7286
7287
7288 /* Apply a move to the given board  */
7289 void
7290 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7291      int fromX, fromY, toX, toY;
7292      int promoChar;
7293      Board board;
7294      char *castling;
7295      char *ep;
7296 {
7297   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7298
7299     /* [HGM] compute & store e.p. status and castling rights for new position */
7300     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7301     { int i;
7302
7303       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7304       oldEP = *ep;
7305       *ep = EP_NONE;
7306
7307       if( board[toY][toX] != EmptySquare ) 
7308            *ep = EP_CAPTURE;  
7309
7310       if( board[fromY][fromX] == WhitePawn ) {
7311            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7312                *ep = EP_PAWN_MOVE;
7313            if( toY-fromY==2) {
7314                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7315                         gameInfo.variant != VariantBerolina || toX < fromX)
7316                       *ep = toX | berolina;
7317                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7318                         gameInfo.variant != VariantBerolina || toX > fromX) 
7319                       *ep = toX;
7320            }
7321       } else 
7322       if( board[fromY][fromX] == BlackPawn ) {
7323            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7324                *ep = EP_PAWN_MOVE; 
7325            if( toY-fromY== -2) {
7326                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7327                         gameInfo.variant != VariantBerolina || toX < fromX)
7328                       *ep = toX | berolina;
7329                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7330                         gameInfo.variant != VariantBerolina || toX > fromX) 
7331                       *ep = toX;
7332            }
7333        }
7334
7335        for(i=0; i<nrCastlingRights; i++) {
7336            if(castling[i] == fromX && castlingRank[i] == fromY ||
7337               castling[i] == toX   && castlingRank[i] == toY   
7338              ) castling[i] = -1; // revoke for moved or captured piece
7339        }
7340
7341     }
7342
7343   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7344   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7345        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7346          
7347   if (fromX == toX && fromY == toY) return;
7348
7349   if (fromY == DROP_RANK) {
7350         /* must be first */
7351         piece = board[toY][toX] = (ChessSquare) fromX;
7352   } else {
7353      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7354      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7355      if(gameInfo.variant == VariantKnightmate)
7356          king += (int) WhiteUnicorn - (int) WhiteKing;
7357
7358     /* Code added by Tord: */
7359     /* FRC castling assumed when king captures friendly rook. */
7360     if (board[fromY][fromX] == WhiteKing &&
7361              board[toY][toX] == WhiteRook) {
7362       board[fromY][fromX] = EmptySquare;
7363       board[toY][toX] = EmptySquare;
7364       if(toX > fromX) {
7365         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7366       } else {
7367         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7368       }
7369     } else if (board[fromY][fromX] == BlackKing &&
7370                board[toY][toX] == BlackRook) {
7371       board[fromY][fromX] = EmptySquare;
7372       board[toY][toX] = EmptySquare;
7373       if(toX > fromX) {
7374         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7375       } else {
7376         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7377       }
7378     /* End of code added by Tord */
7379
7380     } else if (board[fromY][fromX] == king
7381         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7382         && toY == fromY && toX > fromX+1) {
7383         board[fromY][fromX] = EmptySquare;
7384         board[toY][toX] = king;
7385         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7386         board[fromY][BOARD_RGHT-1] = EmptySquare;
7387     } else if (board[fromY][fromX] == king
7388         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7389                && toY == fromY && toX < fromX-1) {
7390         board[fromY][fromX] = EmptySquare;
7391         board[toY][toX] = king;
7392         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7393         board[fromY][BOARD_LEFT] = EmptySquare;
7394     } else if (board[fromY][fromX] == WhitePawn
7395                && toY == BOARD_HEIGHT-1
7396                && gameInfo.variant != VariantXiangqi
7397                ) {
7398         /* white pawn promotion */
7399         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7400         if (board[toY][toX] == EmptySquare) {
7401             board[toY][toX] = WhiteQueen;
7402         }
7403         if(gameInfo.variant==VariantBughouse ||
7404            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7405             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7406         board[fromY][fromX] = EmptySquare;
7407     } else if ((fromY == BOARD_HEIGHT-4)
7408                && (toX != fromX)
7409                && gameInfo.variant != VariantXiangqi
7410                && gameInfo.variant != VariantBerolina
7411                && (board[fromY][fromX] == WhitePawn)
7412                && (board[toY][toX] == EmptySquare)) {
7413         board[fromY][fromX] = EmptySquare;
7414         board[toY][toX] = WhitePawn;
7415         captured = board[toY - 1][toX];
7416         board[toY - 1][toX] = EmptySquare;
7417     } else if ((fromY == BOARD_HEIGHT-4)
7418                && (toX == fromX)
7419                && gameInfo.variant == VariantBerolina
7420                && (board[fromY][fromX] == WhitePawn)
7421                && (board[toY][toX] == EmptySquare)) {
7422         board[fromY][fromX] = EmptySquare;
7423         board[toY][toX] = WhitePawn;
7424         if(oldEP & EP_BEROLIN_A) {
7425                 captured = board[fromY][fromX-1];
7426                 board[fromY][fromX-1] = EmptySquare;
7427         }else{  captured = board[fromY][fromX+1];
7428                 board[fromY][fromX+1] = EmptySquare;
7429         }
7430     } else if (board[fromY][fromX] == king
7431         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7432                && toY == fromY && toX > fromX+1) {
7433         board[fromY][fromX] = EmptySquare;
7434         board[toY][toX] = king;
7435         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7436         board[fromY][BOARD_RGHT-1] = EmptySquare;
7437     } else if (board[fromY][fromX] == king
7438         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7439                && toY == fromY && toX < fromX-1) {
7440         board[fromY][fromX] = EmptySquare;
7441         board[toY][toX] = king;
7442         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7443         board[fromY][BOARD_LEFT] = EmptySquare;
7444     } else if (fromY == 7 && fromX == 3
7445                && board[fromY][fromX] == BlackKing
7446                && toY == 7 && toX == 5) {
7447         board[fromY][fromX] = EmptySquare;
7448         board[toY][toX] = BlackKing;
7449         board[fromY][7] = EmptySquare;
7450         board[toY][4] = BlackRook;
7451     } else if (fromY == 7 && fromX == 3
7452                && board[fromY][fromX] == BlackKing
7453                && toY == 7 && toX == 1) {
7454         board[fromY][fromX] = EmptySquare;
7455         board[toY][toX] = BlackKing;
7456         board[fromY][0] = EmptySquare;
7457         board[toY][2] = BlackRook;
7458     } else if (board[fromY][fromX] == BlackPawn
7459                && toY == 0
7460                && gameInfo.variant != VariantXiangqi
7461                ) {
7462         /* black pawn promotion */
7463         board[0][toX] = CharToPiece(ToLower(promoChar));
7464         if (board[0][toX] == EmptySquare) {
7465             board[0][toX] = BlackQueen;
7466         }
7467         if(gameInfo.variant==VariantBughouse ||
7468            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7469             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7470         board[fromY][fromX] = EmptySquare;
7471     } else if ((fromY == 3)
7472                && (toX != fromX)
7473                && gameInfo.variant != VariantXiangqi
7474                && gameInfo.variant != VariantBerolina
7475                && (board[fromY][fromX] == BlackPawn)
7476                && (board[toY][toX] == EmptySquare)) {
7477         board[fromY][fromX] = EmptySquare;
7478         board[toY][toX] = BlackPawn;
7479         captured = board[toY + 1][toX];
7480         board[toY + 1][toX] = EmptySquare;
7481     } else if ((fromY == 3)
7482                && (toX == fromX)
7483                && gameInfo.variant == VariantBerolina
7484                && (board[fromY][fromX] == BlackPawn)
7485                && (board[toY][toX] == EmptySquare)) {
7486         board[fromY][fromX] = EmptySquare;
7487         board[toY][toX] = BlackPawn;
7488         if(oldEP & EP_BEROLIN_A) {
7489                 captured = board[fromY][fromX-1];
7490                 board[fromY][fromX-1] = EmptySquare;
7491         }else{  captured = board[fromY][fromX+1];
7492                 board[fromY][fromX+1] = EmptySquare;
7493         }
7494     } else {
7495         board[toY][toX] = board[fromY][fromX];
7496         board[fromY][fromX] = EmptySquare;
7497     }
7498
7499     /* [HGM] now we promote for Shogi, if needed */
7500     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7501         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7502   }
7503
7504     if (gameInfo.holdingsWidth != 0) {
7505
7506       /* !!A lot more code needs to be written to support holdings  */
7507       /* [HGM] OK, so I have written it. Holdings are stored in the */
7508       /* penultimate board files, so they are automaticlly stored   */
7509       /* in the game history.                                       */
7510       if (fromY == DROP_RANK) {
7511         /* Delete from holdings, by decreasing count */
7512         /* and erasing image if necessary            */
7513         p = (int) fromX;
7514         if(p < (int) BlackPawn) { /* white drop */
7515              p -= (int)WhitePawn;
7516              if(p >= gameInfo.holdingsSize) p = 0;
7517              if(--board[p][BOARD_WIDTH-2] == 0)
7518                   board[p][BOARD_WIDTH-1] = EmptySquare;
7519         } else {                  /* black drop */
7520              p -= (int)BlackPawn;
7521              if(p >= gameInfo.holdingsSize) p = 0;
7522              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7523                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7524         }
7525       }
7526       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7527           && gameInfo.variant != VariantBughouse        ) {
7528         /* [HGM] holdings: Add to holdings, if holdings exist */
7529         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7530                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7531                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7532         }
7533         p = (int) captured;
7534         if (p >= (int) BlackPawn) {
7535           p -= (int)BlackPawn;
7536           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7537                   /* in Shogi restore piece to its original  first */
7538                   captured = (ChessSquare) (DEMOTED captured);
7539                   p = DEMOTED p;
7540           }
7541           p = PieceToNumber((ChessSquare)p);
7542           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7543           board[p][BOARD_WIDTH-2]++;
7544           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7545         } else {
7546           p -= (int)WhitePawn;
7547           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7548                   captured = (ChessSquare) (DEMOTED captured);
7549                   p = DEMOTED p;
7550           }
7551           p = PieceToNumber((ChessSquare)p);
7552           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7553           board[BOARD_HEIGHT-1-p][1]++;
7554           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7555         }
7556       }
7557
7558     } else if (gameInfo.variant == VariantAtomic) {
7559       if (captured != EmptySquare) {
7560         int y, x;
7561         for (y = toY-1; y <= toY+1; y++) {
7562           for (x = toX-1; x <= toX+1; x++) {
7563             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7564                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7565               board[y][x] = EmptySquare;
7566             }
7567           }
7568         }
7569         board[toY][toX] = EmptySquare;
7570       }
7571     }
7572     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7573         /* [HGM] Shogi promotions */
7574         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7575     }
7576
7577     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7578                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7579         // [HGM] superchess: take promotion piece out of holdings
7580         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7581         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7582             if(!--board[k][BOARD_WIDTH-2])
7583                 board[k][BOARD_WIDTH-1] = EmptySquare;
7584         } else {
7585             if(!--board[BOARD_HEIGHT-1-k][1])
7586                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7587         }
7588     }
7589
7590 }
7591
7592 /* Updates forwardMostMove */
7593 void
7594 MakeMove(fromX, fromY, toX, toY, promoChar)
7595      int fromX, fromY, toX, toY;
7596      int promoChar;
7597 {
7598 //    forwardMostMove++; // [HGM] bare: moved downstream
7599
7600     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7601         int timeLeft; static int lastLoadFlag=0; int king, piece;
7602         piece = boards[forwardMostMove][fromY][fromX];
7603         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7604         if(gameInfo.variant == VariantKnightmate)
7605             king += (int) WhiteUnicorn - (int) WhiteKing;
7606         if(forwardMostMove == 0) {
7607             if(blackPlaysFirst) 
7608                 fprintf(serverMoves, "%s;", second.tidy);
7609             fprintf(serverMoves, "%s;", first.tidy);
7610             if(!blackPlaysFirst) 
7611                 fprintf(serverMoves, "%s;", second.tidy);
7612         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7613         lastLoadFlag = loadFlag;
7614         // print base move
7615         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7616         // print castling suffix
7617         if( toY == fromY && piece == king ) {
7618             if(toX-fromX > 1)
7619                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7620             if(fromX-toX >1)
7621                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7622         }
7623         // e.p. suffix
7624         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7625              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7626              boards[forwardMostMove][toY][toX] == EmptySquare
7627              && fromX != toX )
7628                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7629         // promotion suffix
7630         if(promoChar != NULLCHAR)
7631                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7632         if(!loadFlag) {
7633             fprintf(serverMoves, "/%d/%d",
7634                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7635             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7636             else                      timeLeft = blackTimeRemaining/1000;
7637             fprintf(serverMoves, "/%d", timeLeft);
7638         }
7639         fflush(serverMoves);
7640     }
7641
7642     if (forwardMostMove+1 >= MAX_MOVES) {
7643       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7644                         0, 1);
7645       return;
7646     }
7647     if (commentList[forwardMostMove+1] != NULL) {
7648         free(commentList[forwardMostMove+1]);
7649         commentList[forwardMostMove+1] = NULL;
7650     }
7651     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7652     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7653     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7654                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7655     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7656     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7657     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7658     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7659     gameInfo.result = GameUnfinished;
7660     if (gameInfo.resultDetails != NULL) {
7661         free(gameInfo.resultDetails);
7662         gameInfo.resultDetails = NULL;
7663     }
7664     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7665                               moveList[forwardMostMove - 1]);
7666     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7667                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7668                              fromY, fromX, toY, toX, promoChar,
7669                              parseList[forwardMostMove - 1]);
7670     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7671                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7672                             castlingRights[forwardMostMove]) ) {
7673       case MT_NONE:
7674       case MT_STALEMATE:
7675       default:
7676         break;
7677       case MT_CHECK:
7678         if(gameInfo.variant != VariantShogi)
7679             strcat(parseList[forwardMostMove - 1], "+");
7680         break;
7681       case MT_CHECKMATE:
7682       case MT_STAINMATE:
7683         strcat(parseList[forwardMostMove - 1], "#");
7684         break;
7685     }
7686     if (appData.debugMode) {
7687         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7688     }
7689
7690 }
7691
7692 /* Updates currentMove if not pausing */
7693 void
7694 ShowMove(fromX, fromY, toX, toY)
7695 {
7696     int instant = (gameMode == PlayFromGameFile) ?
7697         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7698     if(appData.noGUI) return;
7699     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7700         if (!instant) {
7701             if (forwardMostMove == currentMove + 1) {
7702                 AnimateMove(boards[forwardMostMove - 1],
7703                             fromX, fromY, toX, toY);
7704             }
7705             if (appData.highlightLastMove) {
7706                 SetHighlights(fromX, fromY, toX, toY);
7707             }
7708         }
7709         currentMove = forwardMostMove;
7710     }
7711
7712     if (instant) return;
7713
7714     DisplayMove(currentMove - 1);
7715     DrawPosition(FALSE, boards[currentMove]);
7716     DisplayBothClocks();
7717     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7718 }
7719
7720 void SendEgtPath(ChessProgramState *cps)
7721 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7722         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7723
7724         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7725
7726         while(*p) {
7727             char c, *q = name+1, *r, *s;
7728
7729             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7730             while(*p && *p != ',') *q++ = *p++;
7731             *q++ = ':'; *q = 0;
7732             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7733                 strcmp(name, ",nalimov:") == 0 ) {
7734                 // take nalimov path from the menu-changeable option first, if it is defined
7735                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7736                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7737             } else
7738             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7739                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7740                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7741                 s = r = StrStr(s, ":") + 1; // beginning of path info
7742                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7743                 c = *r; *r = 0;             // temporarily null-terminate path info
7744                     *--q = 0;               // strip of trailig ':' from name
7745                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7746                 *r = c;
7747                 SendToProgram(buf,cps);     // send egtbpath command for this format
7748             }
7749             if(*p == ',') p++; // read away comma to position for next format name
7750         }
7751 }
7752
7753 void
7754 InitChessProgram(cps, setup)
7755      ChessProgramState *cps;
7756      int setup; /* [HGM] needed to setup FRC opening position */
7757 {
7758     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7759     if (appData.noChessProgram) return;
7760     hintRequested = FALSE;
7761     bookRequested = FALSE;
7762
7763     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7764     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7765     if(cps->memSize) { /* [HGM] memory */
7766         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7767         SendToProgram(buf, cps);
7768     }
7769     SendEgtPath(cps); /* [HGM] EGT */
7770     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7771         sprintf(buf, "cores %d\n", appData.smpCores);
7772         SendToProgram(buf, cps);
7773     }
7774
7775     SendToProgram(cps->initString, cps);
7776     if (gameInfo.variant != VariantNormal &&
7777         gameInfo.variant != VariantLoadable
7778         /* [HGM] also send variant if board size non-standard */
7779         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7780                                             ) {
7781       char *v = VariantName(gameInfo.variant);
7782       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7783         /* [HGM] in protocol 1 we have to assume all variants valid */
7784         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7785         DisplayFatalError(buf, 0, 1);
7786         return;
7787       }
7788
7789       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7790       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7791       if( gameInfo.variant == VariantXiangqi )
7792            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7793       if( gameInfo.variant == VariantShogi )
7794            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7795       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7796            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7797       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7798                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7799            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7800       if( gameInfo.variant == VariantCourier )
7801            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7802       if( gameInfo.variant == VariantSuper )
7803            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7804       if( gameInfo.variant == VariantGreat )
7805            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7806
7807       if(overruled) {
7808            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7809                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7810            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7811            if(StrStr(cps->variants, b) == NULL) { 
7812                // specific sized variant not known, check if general sizing allowed
7813                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7814                    if(StrStr(cps->variants, "boardsize") == NULL) {
7815                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7816                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7817                        DisplayFatalError(buf, 0, 1);
7818                        return;
7819                    }
7820                    /* [HGM] here we really should compare with the maximum supported board size */
7821                }
7822            }
7823       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7824       sprintf(buf, "variant %s\n", b);
7825       SendToProgram(buf, cps);
7826     }
7827     currentlyInitializedVariant = gameInfo.variant;
7828
7829     /* [HGM] send opening position in FRC to first engine */
7830     if(setup) {
7831           SendToProgram("force\n", cps);
7832           SendBoard(cps, 0);
7833           /* engine is now in force mode! Set flag to wake it up after first move. */
7834           setboardSpoiledMachineBlack = 1;
7835     }
7836
7837     if (cps->sendICS) {
7838       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7839       SendToProgram(buf, cps);
7840     }
7841     cps->maybeThinking = FALSE;
7842     cps->offeredDraw = 0;
7843     if (!appData.icsActive) {
7844         SendTimeControl(cps, movesPerSession, timeControl,
7845                         timeIncrement, appData.searchDepth,
7846                         searchTime);
7847     }
7848     if (appData.showThinking 
7849         // [HGM] thinking: four options require thinking output to be sent
7850         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7851                                 ) {
7852         SendToProgram("post\n", cps);
7853     }
7854     SendToProgram("hard\n", cps);
7855     if (!appData.ponderNextMove) {
7856         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7857            it without being sure what state we are in first.  "hard"
7858            is not a toggle, so that one is OK.
7859          */
7860         SendToProgram("easy\n", cps);
7861     }
7862     if (cps->usePing) {
7863       sprintf(buf, "ping %d\n", ++cps->lastPing);
7864       SendToProgram(buf, cps);
7865     }
7866     cps->initDone = TRUE;
7867 }   
7868
7869
7870 void
7871 StartChessProgram(cps)
7872      ChessProgramState *cps;
7873 {
7874     char buf[MSG_SIZ];
7875     int err;
7876
7877     if (appData.noChessProgram) return;
7878     cps->initDone = FALSE;
7879
7880     if (strcmp(cps->host, "localhost") == 0) {
7881         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7882     } else if (*appData.remoteShell == NULLCHAR) {
7883         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7884     } else {
7885         if (*appData.remoteUser == NULLCHAR) {
7886           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7887                     cps->program);
7888         } else {
7889           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7890                     cps->host, appData.remoteUser, cps->program);
7891         }
7892         err = StartChildProcess(buf, "", &cps->pr);
7893     }
7894     
7895     if (err != 0) {
7896         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7897         DisplayFatalError(buf, err, 1);
7898         cps->pr = NoProc;
7899         cps->isr = NULL;
7900         return;
7901     }
7902     
7903     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7904     if (cps->protocolVersion > 1) {
7905       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7906       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7907       cps->comboCnt = 0;  //                and values of combo boxes
7908       SendToProgram(buf, cps);
7909     } else {
7910       SendToProgram("xboard\n", cps);
7911     }
7912 }
7913
7914
7915 void
7916 TwoMachinesEventIfReady P((void))
7917 {
7918   if (first.lastPing != first.lastPong) {
7919     DisplayMessage("", _("Waiting for first chess program"));
7920     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7921     return;
7922   }
7923   if (second.lastPing != second.lastPong) {
7924     DisplayMessage("", _("Waiting for second chess program"));
7925     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7926     return;
7927   }
7928   ThawUI();
7929   TwoMachinesEvent();
7930 }
7931
7932 void
7933 NextMatchGame P((void))
7934 {
7935     int index; /* [HGM] autoinc: step lod index during match */
7936     Reset(FALSE, TRUE);
7937     if (*appData.loadGameFile != NULLCHAR) {
7938         index = appData.loadGameIndex;
7939         if(index < 0) { // [HGM] autoinc
7940             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7941             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7942         } 
7943         LoadGameFromFile(appData.loadGameFile,
7944                          index,
7945                          appData.loadGameFile, FALSE);
7946     } else if (*appData.loadPositionFile != NULLCHAR) {
7947         index = appData.loadPositionIndex;
7948         if(index < 0) { // [HGM] autoinc
7949             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7950             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7951         } 
7952         LoadPositionFromFile(appData.loadPositionFile,
7953                              index,
7954                              appData.loadPositionFile);
7955     }
7956     TwoMachinesEventIfReady();
7957 }
7958
7959 void UserAdjudicationEvent( int result )
7960 {
7961     ChessMove gameResult = GameIsDrawn;
7962
7963     if( result > 0 ) {
7964         gameResult = WhiteWins;
7965     }
7966     else if( result < 0 ) {
7967         gameResult = BlackWins;
7968     }
7969
7970     if( gameMode == TwoMachinesPlay ) {
7971         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7972     }
7973 }
7974
7975
7976 // [HGM] save: calculate checksum of game to make games easily identifiable
7977 int StringCheckSum(char *s)
7978 {
7979         int i = 0;
7980         if(s==NULL) return 0;
7981         while(*s) i = i*259 + *s++;
7982         return i;
7983 }
7984
7985 int GameCheckSum()
7986 {
7987         int i, sum=0;
7988         for(i=backwardMostMove; i<forwardMostMove; i++) {
7989                 sum += pvInfoList[i].depth;
7990                 sum += StringCheckSum(parseList[i]);
7991                 sum += StringCheckSum(commentList[i]);
7992                 sum *= 261;
7993         }
7994         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7995         return sum + StringCheckSum(commentList[i]);
7996 } // end of save patch
7997
7998 void
7999 GameEnds(result, resultDetails, whosays)
8000      ChessMove result;
8001      char *resultDetails;
8002      int whosays;
8003 {
8004     GameMode nextGameMode;
8005     int isIcsGame;
8006     char buf[MSG_SIZ];
8007
8008     if(endingGame) return; /* [HGM] crash: forbid recursion */
8009     endingGame = 1;
8010
8011     if (appData.debugMode) {
8012       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8013               result, resultDetails ? resultDetails : "(null)", whosays);
8014     }
8015
8016     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8017         /* If we are playing on ICS, the server decides when the
8018            game is over, but the engine can offer to draw, claim 
8019            a draw, or resign. 
8020          */
8021 #if ZIPPY
8022         if (appData.zippyPlay && first.initDone) {
8023             if (result == GameIsDrawn) {
8024                 /* In case draw still needs to be claimed */
8025                 SendToICS(ics_prefix);
8026                 SendToICS("draw\n");
8027             } else if (StrCaseStr(resultDetails, "resign")) {
8028                 SendToICS(ics_prefix);
8029                 SendToICS("resign\n");
8030             }
8031         }
8032 #endif
8033         endingGame = 0; /* [HGM] crash */
8034         return;
8035     }
8036
8037     /* If we're loading the game from a file, stop */
8038     if (whosays == GE_FILE) {
8039       (void) StopLoadGameTimer();
8040       gameFileFP = NULL;
8041     }
8042
8043     /* Cancel draw offers */
8044     first.offeredDraw = second.offeredDraw = 0;
8045
8046     /* If this is an ICS game, only ICS can really say it's done;
8047        if not, anyone can. */
8048     isIcsGame = (gameMode == IcsPlayingWhite || 
8049                  gameMode == IcsPlayingBlack || 
8050                  gameMode == IcsObserving    || 
8051                  gameMode == IcsExamining);
8052
8053     if (!isIcsGame || whosays == GE_ICS) {
8054         /* OK -- not an ICS game, or ICS said it was done */
8055         StopClocks();
8056         if (!isIcsGame && !appData.noChessProgram) 
8057           SetUserThinkingEnables();
8058     
8059         /* [HGM] if a machine claims the game end we verify this claim */
8060         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8061             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8062                 char claimer;
8063                 ChessMove trueResult = (ChessMove) -1;
8064
8065                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8066                                             first.twoMachinesColor[0] :
8067                                             second.twoMachinesColor[0] ;
8068
8069                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8070                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8071                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8072                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8073                 } else
8074                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8075                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8076                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8077                 } else
8078                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8079                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8080                 }
8081
8082                 // now verify win claims, but not in drop games, as we don't understand those yet
8083                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8084                                                  || gameInfo.variant == VariantGreat) &&
8085                     (result == WhiteWins && claimer == 'w' ||
8086                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8087                       if (appData.debugMode) {
8088                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8089                                 result, epStatus[forwardMostMove], forwardMostMove);
8090                       }
8091                       if(result != trueResult) {
8092                               sprintf(buf, "False win claim: '%s'", resultDetails);
8093                               result = claimer == 'w' ? BlackWins : WhiteWins;
8094                               resultDetails = buf;
8095                       }
8096                 } else
8097                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8098                     && (forwardMostMove <= backwardMostMove ||
8099                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8100                         (claimer=='b')==(forwardMostMove&1))
8101                                                                                   ) {
8102                       /* [HGM] verify: draws that were not flagged are false claims */
8103                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8104                       result = claimer == 'w' ? BlackWins : WhiteWins;
8105                       resultDetails = buf;
8106                 }
8107                 /* (Claiming a loss is accepted no questions asked!) */
8108             }
8109             /* [HGM] bare: don't allow bare King to win */
8110             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8111                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8112                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8113                && result != GameIsDrawn)
8114             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8115                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8116                         int p = (int)boards[forwardMostMove][i][j] - color;
8117                         if(p >= 0 && p <= (int)WhiteKing) k++;
8118                 }
8119                 if (appData.debugMode) {
8120                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8121                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8122                 }
8123                 if(k <= 1) {
8124                         result = GameIsDrawn;
8125                         sprintf(buf, "%s but bare king", resultDetails);
8126                         resultDetails = buf;
8127                 }
8128             }
8129         }
8130
8131
8132         if(serverMoves != NULL && !loadFlag) { char c = '=';
8133             if(result==WhiteWins) c = '+';
8134             if(result==BlackWins) c = '-';
8135             if(resultDetails != NULL)
8136                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8137         }
8138         if (resultDetails != NULL) {
8139             gameInfo.result = result;
8140             gameInfo.resultDetails = StrSave(resultDetails);
8141
8142             /* display last move only if game was not loaded from file */
8143             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8144                 DisplayMove(currentMove - 1);
8145     
8146             if (forwardMostMove != 0) {
8147                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8148                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8149                                                                 ) {
8150                     if (*appData.saveGameFile != NULLCHAR) {
8151                         SaveGameToFile(appData.saveGameFile, TRUE);
8152                     } else if (appData.autoSaveGames) {
8153                         AutoSaveGame();
8154                     }
8155                     if (*appData.savePositionFile != NULLCHAR) {
8156                         SavePositionToFile(appData.savePositionFile);
8157                     }
8158                 }
8159             }
8160
8161             /* Tell program how game ended in case it is learning */
8162             /* [HGM] Moved this to after saving the PGN, just in case */
8163             /* engine died and we got here through time loss. In that */
8164             /* case we will get a fatal error writing the pipe, which */
8165             /* would otherwise lose us the PGN.                       */
8166             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8167             /* output during GameEnds should never be fatal anymore   */
8168             if (gameMode == MachinePlaysWhite ||
8169                 gameMode == MachinePlaysBlack ||
8170                 gameMode == TwoMachinesPlay ||
8171                 gameMode == IcsPlayingWhite ||
8172                 gameMode == IcsPlayingBlack ||
8173                 gameMode == BeginningOfGame) {
8174                 char buf[MSG_SIZ];
8175                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8176                         resultDetails);
8177                 if (first.pr != NoProc) {
8178                     SendToProgram(buf, &first);
8179                 }
8180                 if (second.pr != NoProc &&
8181                     gameMode == TwoMachinesPlay) {
8182                     SendToProgram(buf, &second);
8183                 }
8184             }
8185         }
8186
8187         if (appData.icsActive) {
8188             if (appData.quietPlay &&
8189                 (gameMode == IcsPlayingWhite ||
8190                  gameMode == IcsPlayingBlack)) {
8191                 SendToICS(ics_prefix);
8192                 SendToICS("set shout 1\n");
8193             }
8194             nextGameMode = IcsIdle;
8195             ics_user_moved = FALSE;
8196             /* clean up premove.  It's ugly when the game has ended and the
8197              * premove highlights are still on the board.
8198              */
8199             if (gotPremove) {
8200               gotPremove = FALSE;
8201               ClearPremoveHighlights();
8202               DrawPosition(FALSE, boards[currentMove]);
8203             }
8204             if (whosays == GE_ICS) {
8205                 switch (result) {
8206                 case WhiteWins:
8207                     if (gameMode == IcsPlayingWhite)
8208                         PlayIcsWinSound();
8209                     else if(gameMode == IcsPlayingBlack)
8210                         PlayIcsLossSound();
8211                     break;
8212                 case BlackWins:
8213                     if (gameMode == IcsPlayingBlack)
8214                         PlayIcsWinSound();
8215                     else if(gameMode == IcsPlayingWhite)
8216                         PlayIcsLossSound();
8217                     break;
8218                 case GameIsDrawn:
8219                     PlayIcsDrawSound();
8220                     break;
8221                 default:
8222                     PlayIcsUnfinishedSound();
8223                 }
8224             }
8225         } else if (gameMode == EditGame ||
8226                    gameMode == PlayFromGameFile || 
8227                    gameMode == AnalyzeMode || 
8228                    gameMode == AnalyzeFile) {
8229             nextGameMode = gameMode;
8230         } else {
8231             nextGameMode = EndOfGame;
8232         }
8233         pausing = FALSE;
8234         ModeHighlight();
8235     } else {
8236         nextGameMode = gameMode;
8237     }
8238
8239     if (appData.noChessProgram) {
8240         gameMode = nextGameMode;
8241         ModeHighlight();
8242         endingGame = 0; /* [HGM] crash */
8243         return;
8244     }
8245
8246     if (first.reuse) {
8247         /* Put first chess program into idle state */
8248         if (first.pr != NoProc &&
8249             (gameMode == MachinePlaysWhite ||
8250              gameMode == MachinePlaysBlack ||
8251              gameMode == TwoMachinesPlay ||
8252              gameMode == IcsPlayingWhite ||
8253              gameMode == IcsPlayingBlack ||
8254              gameMode == BeginningOfGame)) {
8255             SendToProgram("force\n", &first);
8256             if (first.usePing) {
8257               char buf[MSG_SIZ];
8258               sprintf(buf, "ping %d\n", ++first.lastPing);
8259               SendToProgram(buf, &first);
8260             }
8261         }
8262     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8263         /* Kill off first chess program */
8264         if (first.isr != NULL)
8265           RemoveInputSource(first.isr);
8266         first.isr = NULL;
8267     
8268         if (first.pr != NoProc) {
8269             ExitAnalyzeMode();
8270             DoSleep( appData.delayBeforeQuit );
8271             SendToProgram("quit\n", &first);
8272             DoSleep( appData.delayAfterQuit );
8273             DestroyChildProcess(first.pr, first.useSigterm);
8274         }
8275         first.pr = NoProc;
8276     }
8277     if (second.reuse) {
8278         /* Put second chess program into idle state */
8279         if (second.pr != NoProc &&
8280             gameMode == TwoMachinesPlay) {
8281             SendToProgram("force\n", &second);
8282             if (second.usePing) {
8283               char buf[MSG_SIZ];
8284               sprintf(buf, "ping %d\n", ++second.lastPing);
8285               SendToProgram(buf, &second);
8286             }
8287         }
8288     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8289         /* Kill off second chess program */
8290         if (second.isr != NULL)
8291           RemoveInputSource(second.isr);
8292         second.isr = NULL;
8293     
8294         if (second.pr != NoProc) {
8295             DoSleep( appData.delayBeforeQuit );
8296             SendToProgram("quit\n", &second);
8297             DoSleep( appData.delayAfterQuit );
8298             DestroyChildProcess(second.pr, second.useSigterm);
8299         }
8300         second.pr = NoProc;
8301     }
8302
8303     if (matchMode && gameMode == TwoMachinesPlay) {
8304         switch (result) {
8305         case WhiteWins:
8306           if (first.twoMachinesColor[0] == 'w') {
8307             first.matchWins++;
8308           } else {
8309             second.matchWins++;
8310           }
8311           break;
8312         case BlackWins:
8313           if (first.twoMachinesColor[0] == 'b') {
8314             first.matchWins++;
8315           } else {
8316             second.matchWins++;
8317           }
8318           break;
8319         default:
8320           break;
8321         }
8322         if (matchGame < appData.matchGames) {
8323             char *tmp;
8324             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8325                 tmp = first.twoMachinesColor;
8326                 first.twoMachinesColor = second.twoMachinesColor;
8327                 second.twoMachinesColor = tmp;
8328             }
8329             gameMode = nextGameMode;
8330             matchGame++;
8331             if(appData.matchPause>10000 || appData.matchPause<10)
8332                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8333             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8334             endingGame = 0; /* [HGM] crash */
8335             return;
8336         } else {
8337             char buf[MSG_SIZ];
8338             gameMode = nextGameMode;
8339             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8340                     first.tidy, second.tidy,
8341                     first.matchWins, second.matchWins,
8342                     appData.matchGames - (first.matchWins + second.matchWins));
8343             DisplayFatalError(buf, 0, 0);
8344         }
8345     }
8346     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8347         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8348       ExitAnalyzeMode();
8349     gameMode = nextGameMode;
8350     ModeHighlight();
8351     endingGame = 0;  /* [HGM] crash */
8352 }
8353
8354 /* Assumes program was just initialized (initString sent).
8355    Leaves program in force mode. */
8356 void
8357 FeedMovesToProgram(cps, upto) 
8358      ChessProgramState *cps;
8359      int upto;
8360 {
8361     int i;
8362     
8363     if (appData.debugMode)
8364       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8365               startedFromSetupPosition ? "position and " : "",
8366               backwardMostMove, upto, cps->which);
8367     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8368         // [HGM] variantswitch: make engine aware of new variant
8369         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8370                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8371         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8372         SendToProgram(buf, cps);
8373         currentlyInitializedVariant = gameInfo.variant;
8374     }
8375     SendToProgram("force\n", cps);
8376     if (startedFromSetupPosition) {
8377         SendBoard(cps, backwardMostMove);
8378     if (appData.debugMode) {
8379         fprintf(debugFP, "feedMoves\n");
8380     }
8381     }
8382     for (i = backwardMostMove; i < upto; i++) {
8383         SendMoveToProgram(i, cps);
8384     }
8385 }
8386
8387
8388 void
8389 ResurrectChessProgram()
8390 {
8391      /* The chess program may have exited.
8392         If so, restart it and feed it all the moves made so far. */
8393
8394     if (appData.noChessProgram || first.pr != NoProc) return;
8395     
8396     StartChessProgram(&first);
8397     InitChessProgram(&first, FALSE);
8398     FeedMovesToProgram(&first, currentMove);
8399
8400     if (!first.sendTime) {
8401         /* can't tell gnuchess what its clock should read,
8402            so we bow to its notion. */
8403         ResetClocks();
8404         timeRemaining[0][currentMove] = whiteTimeRemaining;
8405         timeRemaining[1][currentMove] = blackTimeRemaining;
8406     }
8407
8408     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8409                 appData.icsEngineAnalyze) && first.analysisSupport) {
8410       SendToProgram("analyze\n", &first);
8411       first.analyzing = TRUE;
8412     }
8413 }
8414
8415 /*
8416  * Button procedures
8417  */
8418 void
8419 Reset(redraw, init)
8420      int redraw, init;
8421 {
8422     int i;
8423
8424     if (appData.debugMode) {
8425         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8426                 redraw, init, gameMode);
8427     }
8428     pausing = pauseExamInvalid = FALSE;
8429     startedFromSetupPosition = blackPlaysFirst = FALSE;
8430     firstMove = TRUE;
8431     whiteFlag = blackFlag = FALSE;
8432     userOfferedDraw = FALSE;
8433     hintRequested = bookRequested = FALSE;
8434     first.maybeThinking = FALSE;
8435     second.maybeThinking = FALSE;
8436     first.bookSuspend = FALSE; // [HGM] book
8437     second.bookSuspend = FALSE;
8438     thinkOutput[0] = NULLCHAR;
8439     lastHint[0] = NULLCHAR;
8440     ClearGameInfo(&gameInfo);
8441     gameInfo.variant = StringToVariant(appData.variant);
8442     ics_user_moved = ics_clock_paused = FALSE;
8443     ics_getting_history = H_FALSE;
8444     ics_gamenum = -1;
8445     white_holding[0] = black_holding[0] = NULLCHAR;
8446     ClearProgramStats();
8447     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8448     
8449     ResetFrontEnd();
8450     ClearHighlights();
8451     flipView = appData.flipView;
8452     ClearPremoveHighlights();
8453     gotPremove = FALSE;
8454     alarmSounded = FALSE;
8455
8456     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8457     if(appData.serverMovesName != NULL) {
8458         /* [HGM] prepare to make moves file for broadcasting */
8459         clock_t t = clock();
8460         if(serverMoves != NULL) fclose(serverMoves);
8461         serverMoves = fopen(appData.serverMovesName, "r");
8462         if(serverMoves != NULL) {
8463             fclose(serverMoves);
8464             /* delay 15 sec before overwriting, so all clients can see end */
8465             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8466         }
8467         serverMoves = fopen(appData.serverMovesName, "w");
8468     }
8469
8470     ExitAnalyzeMode();
8471     gameMode = BeginningOfGame;
8472     ModeHighlight();
8473     if(appData.icsActive) gameInfo.variant = VariantNormal;
8474     currentMove = forwardMostMove = backwardMostMove = 0;
8475     InitPosition(redraw);
8476     for (i = 0; i < MAX_MOVES; i++) {
8477         if (commentList[i] != NULL) {
8478             free(commentList[i]);
8479             commentList[i] = NULL;
8480         }
8481     }
8482     ResetClocks();
8483     timeRemaining[0][0] = whiteTimeRemaining;
8484     timeRemaining[1][0] = blackTimeRemaining;
8485     if (first.pr == NULL) {
8486         StartChessProgram(&first);
8487     }
8488     if (init) {
8489             InitChessProgram(&first, startedFromSetupPosition);
8490     }
8491     DisplayTitle("");
8492     DisplayMessage("", "");
8493     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8494     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8495 }
8496
8497 void
8498 AutoPlayGameLoop()
8499 {
8500     for (;;) {
8501         if (!AutoPlayOneMove())
8502           return;
8503         if (matchMode || appData.timeDelay == 0)
8504           continue;
8505         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8506           return;
8507         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8508         break;
8509     }
8510 }
8511
8512
8513 int
8514 AutoPlayOneMove()
8515 {
8516     int fromX, fromY, toX, toY;
8517
8518     if (appData.debugMode) {
8519       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8520     }
8521
8522     if (gameMode != PlayFromGameFile)
8523       return FALSE;
8524
8525     if (currentMove >= forwardMostMove) {
8526       gameMode = EditGame;
8527       ModeHighlight();
8528
8529       /* [AS] Clear current move marker at the end of a game */
8530       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8531
8532       return FALSE;
8533     }
8534     
8535     toX = moveList[currentMove][2] - AAA;
8536     toY = moveList[currentMove][3] - ONE;
8537
8538     if (moveList[currentMove][1] == '@') {
8539         if (appData.highlightLastMove) {
8540             SetHighlights(-1, -1, toX, toY);
8541         }
8542     } else {
8543         fromX = moveList[currentMove][0] - AAA;
8544         fromY = moveList[currentMove][1] - ONE;
8545
8546         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8547
8548         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8549
8550         if (appData.highlightLastMove) {
8551             SetHighlights(fromX, fromY, toX, toY);
8552         }
8553     }
8554     DisplayMove(currentMove);
8555     SendMoveToProgram(currentMove++, &first);
8556     DisplayBothClocks();
8557     DrawPosition(FALSE, boards[currentMove]);
8558     // [HGM] PV info: always display, routine tests if empty
8559     DisplayComment(currentMove - 1, commentList[currentMove]);
8560     return TRUE;
8561 }
8562
8563
8564 int
8565 LoadGameOneMove(readAhead)
8566      ChessMove readAhead;
8567 {
8568     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8569     char promoChar = NULLCHAR;
8570     ChessMove moveType;
8571     char move[MSG_SIZ];
8572     char *p, *q;
8573     
8574     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8575         gameMode != AnalyzeMode && gameMode != Training) {
8576         gameFileFP = NULL;
8577         return FALSE;
8578     }
8579     
8580     yyboardindex = forwardMostMove;
8581     if (readAhead != (ChessMove)0) {
8582       moveType = readAhead;
8583     } else {
8584       if (gameFileFP == NULL)
8585           return FALSE;
8586       moveType = (ChessMove) yylex();
8587     }
8588     
8589     done = FALSE;
8590     switch (moveType) {
8591       case Comment:
8592         if (appData.debugMode) 
8593           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8594         p = yy_text;
8595         if (*p == '{' || *p == '[' || *p == '(') {
8596             p[strlen(p) - 1] = NULLCHAR;
8597             p++;
8598         }
8599
8600         /* append the comment but don't display it */
8601         while (*p == '\n') p++;
8602         AppendComment(currentMove, p);
8603         return TRUE;
8604
8605       case WhiteCapturesEnPassant:
8606       case BlackCapturesEnPassant:
8607       case WhitePromotionChancellor:
8608       case BlackPromotionChancellor:
8609       case WhitePromotionArchbishop:
8610       case BlackPromotionArchbishop:
8611       case WhitePromotionCentaur:
8612       case BlackPromotionCentaur:
8613       case WhitePromotionQueen:
8614       case BlackPromotionQueen:
8615       case WhitePromotionRook:
8616       case BlackPromotionRook:
8617       case WhitePromotionBishop:
8618       case BlackPromotionBishop:
8619       case WhitePromotionKnight:
8620       case BlackPromotionKnight:
8621       case WhitePromotionKing:
8622       case BlackPromotionKing:
8623       case NormalMove:
8624       case WhiteKingSideCastle:
8625       case WhiteQueenSideCastle:
8626       case BlackKingSideCastle:
8627       case BlackQueenSideCastle:
8628       case WhiteKingSideCastleWild:
8629       case WhiteQueenSideCastleWild:
8630       case BlackKingSideCastleWild:
8631       case BlackQueenSideCastleWild:
8632       /* PUSH Fabien */
8633       case WhiteHSideCastleFR:
8634       case WhiteASideCastleFR:
8635       case BlackHSideCastleFR:
8636       case BlackASideCastleFR:
8637       /* POP Fabien */
8638         if (appData.debugMode)
8639           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8640         fromX = currentMoveString[0] - AAA;
8641         fromY = currentMoveString[1] - ONE;
8642         toX = currentMoveString[2] - AAA;
8643         toY = currentMoveString[3] - ONE;
8644         promoChar = currentMoveString[4];
8645         break;
8646
8647       case WhiteDrop:
8648       case BlackDrop:
8649         if (appData.debugMode)
8650           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8651         fromX = moveType == WhiteDrop ?
8652           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8653         (int) CharToPiece(ToLower(currentMoveString[0]));
8654         fromY = DROP_RANK;
8655         toX = currentMoveString[2] - AAA;
8656         toY = currentMoveString[3] - ONE;
8657         break;
8658
8659       case WhiteWins:
8660       case BlackWins:
8661       case GameIsDrawn:
8662       case GameUnfinished:
8663         if (appData.debugMode)
8664           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8665         p = strchr(yy_text, '{');
8666         if (p == NULL) p = strchr(yy_text, '(');
8667         if (p == NULL) {
8668             p = yy_text;
8669             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8670         } else {
8671             q = strchr(p, *p == '{' ? '}' : ')');
8672             if (q != NULL) *q = NULLCHAR;
8673             p++;
8674         }
8675         GameEnds(moveType, p, GE_FILE);
8676         done = TRUE;
8677         if (cmailMsgLoaded) {
8678             ClearHighlights();
8679             flipView = WhiteOnMove(currentMove);
8680             if (moveType == GameUnfinished) flipView = !flipView;
8681             if (appData.debugMode)
8682               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8683         }
8684         break;
8685
8686       case (ChessMove) 0:       /* end of file */
8687         if (appData.debugMode)
8688           fprintf(debugFP, "Parser hit end of file\n");
8689         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8690                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8691           case MT_NONE:
8692           case MT_CHECK:
8693             break;
8694           case MT_CHECKMATE:
8695           case MT_STAINMATE:
8696             if (WhiteOnMove(currentMove)) {
8697                 GameEnds(BlackWins, "Black mates", GE_FILE);
8698             } else {
8699                 GameEnds(WhiteWins, "White mates", GE_FILE);
8700             }
8701             break;
8702           case MT_STALEMATE:
8703             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8704             break;
8705         }
8706         done = TRUE;
8707         break;
8708
8709       case MoveNumberOne:
8710         if (lastLoadGameStart == GNUChessGame) {
8711             /* GNUChessGames have numbers, but they aren't move numbers */
8712             if (appData.debugMode)
8713               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8714                       yy_text, (int) moveType);
8715             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8716         }
8717         /* else fall thru */
8718
8719       case XBoardGame:
8720       case GNUChessGame:
8721       case PGNTag:
8722         /* Reached start of next game in file */
8723         if (appData.debugMode)
8724           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8725         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8726                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8727           case MT_NONE:
8728           case MT_CHECK:
8729             break;
8730           case MT_CHECKMATE:
8731           case MT_STAINMATE:
8732             if (WhiteOnMove(currentMove)) {
8733                 GameEnds(BlackWins, "Black mates", GE_FILE);
8734             } else {
8735                 GameEnds(WhiteWins, "White mates", GE_FILE);
8736             }
8737             break;
8738           case MT_STALEMATE:
8739             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8740             break;
8741         }
8742         done = TRUE;
8743         break;
8744
8745       case PositionDiagram:     /* should not happen; ignore */
8746       case ElapsedTime:         /* ignore */
8747       case NAG:                 /* ignore */
8748         if (appData.debugMode)
8749           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8750                   yy_text, (int) moveType);
8751         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8752
8753       case IllegalMove:
8754         if (appData.testLegality) {
8755             if (appData.debugMode)
8756               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8757             sprintf(move, _("Illegal move: %d.%s%s"),
8758                     (forwardMostMove / 2) + 1,
8759                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8760             DisplayError(move, 0);
8761             done = TRUE;
8762         } else {
8763             if (appData.debugMode)
8764               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8765                       yy_text, currentMoveString);
8766             fromX = currentMoveString[0] - AAA;
8767             fromY = currentMoveString[1] - ONE;
8768             toX = currentMoveString[2] - AAA;
8769             toY = currentMoveString[3] - ONE;
8770             promoChar = currentMoveString[4];
8771         }
8772         break;
8773
8774       case AmbiguousMove:
8775         if (appData.debugMode)
8776           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8777         sprintf(move, _("Ambiguous move: %d.%s%s"),
8778                 (forwardMostMove / 2) + 1,
8779                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8780         DisplayError(move, 0);
8781         done = TRUE;
8782         break;
8783
8784       default:
8785       case ImpossibleMove:
8786         if (appData.debugMode)
8787           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8788         sprintf(move, _("Illegal move: %d.%s%s"),
8789                 (forwardMostMove / 2) + 1,
8790                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8791         DisplayError(move, 0);
8792         done = TRUE;
8793         break;
8794     }
8795
8796     if (done) {
8797         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8798             DrawPosition(FALSE, boards[currentMove]);
8799             DisplayBothClocks();
8800             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8801               DisplayComment(currentMove - 1, commentList[currentMove]);
8802         }
8803         (void) StopLoadGameTimer();
8804         gameFileFP = NULL;
8805         cmailOldMove = forwardMostMove;
8806         return FALSE;
8807     } else {
8808         /* currentMoveString is set as a side-effect of yylex */
8809         strcat(currentMoveString, "\n");
8810         strcpy(moveList[forwardMostMove], currentMoveString);
8811         
8812         thinkOutput[0] = NULLCHAR;
8813         MakeMove(fromX, fromY, toX, toY, promoChar);
8814         currentMove = forwardMostMove;
8815         return TRUE;
8816     }
8817 }
8818
8819 /* Load the nth game from the given file */
8820 int
8821 LoadGameFromFile(filename, n, title, useList)
8822      char *filename;
8823      int n;
8824      char *title;
8825      /*Boolean*/ int useList;
8826 {
8827     FILE *f;
8828     char buf[MSG_SIZ];
8829
8830     if (strcmp(filename, "-") == 0) {
8831         f = stdin;
8832         title = "stdin";
8833     } else {
8834         f = fopen(filename, "rb");
8835         if (f == NULL) {
8836           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8837             DisplayError(buf, errno);
8838             return FALSE;
8839         }
8840     }
8841     if (fseek(f, 0, 0) == -1) {
8842         /* f is not seekable; probably a pipe */
8843         useList = FALSE;
8844     }
8845     if (useList && n == 0) {
8846         int error = GameListBuild(f);
8847         if (error) {
8848             DisplayError(_("Cannot build game list"), error);
8849         } else if (!ListEmpty(&gameList) &&
8850                    ((ListGame *) gameList.tailPred)->number > 1) {
8851             GameListPopUp(f, title);
8852             return TRUE;
8853         }
8854         GameListDestroy();
8855         n = 1;
8856     }
8857     if (n == 0) n = 1;
8858     return LoadGame(f, n, title, FALSE);
8859 }
8860
8861
8862 void
8863 MakeRegisteredMove()
8864 {
8865     int fromX, fromY, toX, toY;
8866     char promoChar;
8867     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8868         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8869           case CMAIL_MOVE:
8870           case CMAIL_DRAW:
8871             if (appData.debugMode)
8872               fprintf(debugFP, "Restoring %s for game %d\n",
8873                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8874     
8875             thinkOutput[0] = NULLCHAR;
8876             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8877             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8878             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8879             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8880             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8881             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8882             MakeMove(fromX, fromY, toX, toY, promoChar);
8883             ShowMove(fromX, fromY, toX, toY);
8884               
8885             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8886                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8887               case MT_NONE:
8888               case MT_CHECK:
8889                 break;
8890                 
8891               case MT_CHECKMATE:
8892               case MT_STAINMATE:
8893                 if (WhiteOnMove(currentMove)) {
8894                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8895                 } else {
8896                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8897                 }
8898                 break;
8899                 
8900               case MT_STALEMATE:
8901                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8902                 break;
8903             }
8904
8905             break;
8906             
8907           case CMAIL_RESIGN:
8908             if (WhiteOnMove(currentMove)) {
8909                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8910             } else {
8911                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8912             }
8913             break;
8914             
8915           case CMAIL_ACCEPT:
8916             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8917             break;
8918               
8919           default:
8920             break;
8921         }
8922     }
8923
8924     return;
8925 }
8926
8927 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8928 int
8929 CmailLoadGame(f, gameNumber, title, useList)
8930      FILE *f;
8931      int gameNumber;
8932      char *title;
8933      int useList;
8934 {
8935     int retVal;
8936
8937     if (gameNumber > nCmailGames) {
8938         DisplayError(_("No more games in this message"), 0);
8939         return FALSE;
8940     }
8941     if (f == lastLoadGameFP) {
8942         int offset = gameNumber - lastLoadGameNumber;
8943         if (offset == 0) {
8944             cmailMsg[0] = NULLCHAR;
8945             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8946                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8947                 nCmailMovesRegistered--;
8948             }
8949             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8950             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8951                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8952             }
8953         } else {
8954             if (! RegisterMove()) return FALSE;
8955         }
8956     }
8957
8958     retVal = LoadGame(f, gameNumber, title, useList);
8959
8960     /* Make move registered during previous look at this game, if any */
8961     MakeRegisteredMove();
8962
8963     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8964         commentList[currentMove]
8965           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8966         DisplayComment(currentMove - 1, commentList[currentMove]);
8967     }
8968
8969     return retVal;
8970 }
8971
8972 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8973 int
8974 ReloadGame(offset)
8975      int offset;
8976 {
8977     int gameNumber = lastLoadGameNumber + offset;
8978     if (lastLoadGameFP == NULL) {
8979         DisplayError(_("No game has been loaded yet"), 0);
8980         return FALSE;
8981     }
8982     if (gameNumber <= 0) {
8983         DisplayError(_("Can't back up any further"), 0);
8984         return FALSE;
8985     }
8986     if (cmailMsgLoaded) {
8987         return CmailLoadGame(lastLoadGameFP, gameNumber,
8988                              lastLoadGameTitle, lastLoadGameUseList);
8989     } else {
8990         return LoadGame(lastLoadGameFP, gameNumber,
8991                         lastLoadGameTitle, lastLoadGameUseList);
8992     }
8993 }
8994
8995
8996
8997 /* Load the nth game from open file f */
8998 int
8999 LoadGame(f, gameNumber, title, useList)
9000      FILE *f;
9001      int gameNumber;
9002      char *title;
9003      int useList;
9004 {
9005     ChessMove cm;
9006     char buf[MSG_SIZ];
9007     int gn = gameNumber;
9008     ListGame *lg = NULL;
9009     int numPGNTags = 0;
9010     int err;
9011     GameMode oldGameMode;
9012     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9013
9014     if (appData.debugMode) 
9015         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9016
9017     if (gameMode == Training )
9018         SetTrainingModeOff();
9019
9020     oldGameMode = gameMode;
9021     if (gameMode != BeginningOfGame) {
9022       Reset(FALSE, TRUE);
9023     }
9024
9025     gameFileFP = f;
9026     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9027         fclose(lastLoadGameFP);
9028     }
9029
9030     if (useList) {
9031         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9032         
9033         if (lg) {
9034             fseek(f, lg->offset, 0);
9035             GameListHighlight(gameNumber);
9036             gn = 1;
9037         }
9038         else {
9039             DisplayError(_("Game number out of range"), 0);
9040             return FALSE;
9041         }
9042     } else {
9043         GameListDestroy();
9044         if (fseek(f, 0, 0) == -1) {
9045             if (f == lastLoadGameFP ?
9046                 gameNumber == lastLoadGameNumber + 1 :
9047                 gameNumber == 1) {
9048                 gn = 1;
9049             } else {
9050                 DisplayError(_("Can't seek on game file"), 0);
9051                 return FALSE;
9052             }
9053         }
9054     }
9055     lastLoadGameFP = f;
9056     lastLoadGameNumber = gameNumber;
9057     strcpy(lastLoadGameTitle, title);
9058     lastLoadGameUseList = useList;
9059
9060     yynewfile(f);
9061
9062     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9063       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9064                 lg->gameInfo.black);
9065             DisplayTitle(buf);
9066     } else if (*title != NULLCHAR) {
9067         if (gameNumber > 1) {
9068             sprintf(buf, "%s %d", title, gameNumber);
9069             DisplayTitle(buf);
9070         } else {
9071             DisplayTitle(title);
9072         }
9073     }
9074
9075     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9076         gameMode = PlayFromGameFile;
9077         ModeHighlight();
9078     }
9079
9080     currentMove = forwardMostMove = backwardMostMove = 0;
9081     CopyBoard(boards[0], initialPosition);
9082     StopClocks();
9083
9084     /*
9085      * Skip the first gn-1 games in the file.
9086      * Also skip over anything that precedes an identifiable 
9087      * start of game marker, to avoid being confused by 
9088      * garbage at the start of the file.  Currently 
9089      * recognized start of game markers are the move number "1",
9090      * the pattern "gnuchess .* game", the pattern
9091      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9092      * A game that starts with one of the latter two patterns
9093      * will also have a move number 1, possibly
9094      * following a position diagram.
9095      * 5-4-02: Let's try being more lenient and allowing a game to
9096      * start with an unnumbered move.  Does that break anything?
9097      */
9098     cm = lastLoadGameStart = (ChessMove) 0;
9099     while (gn > 0) {
9100         yyboardindex = forwardMostMove;
9101         cm = (ChessMove) yylex();
9102         switch (cm) {
9103           case (ChessMove) 0:
9104             if (cmailMsgLoaded) {
9105                 nCmailGames = CMAIL_MAX_GAMES - gn;
9106             } else {
9107                 Reset(TRUE, TRUE);
9108                 DisplayError(_("Game not found in file"), 0);
9109             }
9110             return FALSE;
9111
9112           case GNUChessGame:
9113           case XBoardGame:
9114             gn--;
9115             lastLoadGameStart = cm;
9116             break;
9117             
9118           case MoveNumberOne:
9119             switch (lastLoadGameStart) {
9120               case GNUChessGame:
9121               case XBoardGame:
9122               case PGNTag:
9123                 break;
9124               case MoveNumberOne:
9125               case (ChessMove) 0:
9126                 gn--;           /* count this game */
9127                 lastLoadGameStart = cm;
9128                 break;
9129               default:
9130                 /* impossible */
9131                 break;
9132             }
9133             break;
9134
9135           case PGNTag:
9136             switch (lastLoadGameStart) {
9137               case GNUChessGame:
9138               case PGNTag:
9139               case MoveNumberOne:
9140               case (ChessMove) 0:
9141                 gn--;           /* count this game */
9142                 lastLoadGameStart = cm;
9143                 break;
9144               case XBoardGame:
9145                 lastLoadGameStart = cm; /* game counted already */
9146                 break;
9147               default:
9148                 /* impossible */
9149                 break;
9150             }
9151             if (gn > 0) {
9152                 do {
9153                     yyboardindex = forwardMostMove;
9154                     cm = (ChessMove) yylex();
9155                 } while (cm == PGNTag || cm == Comment);
9156             }
9157             break;
9158
9159           case WhiteWins:
9160           case BlackWins:
9161           case GameIsDrawn:
9162             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9163                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9164                     != CMAIL_OLD_RESULT) {
9165                     nCmailResults ++ ;
9166                     cmailResult[  CMAIL_MAX_GAMES
9167                                 - gn - 1] = CMAIL_OLD_RESULT;
9168                 }
9169             }
9170             break;
9171
9172           case NormalMove:
9173             /* Only a NormalMove can be at the start of a game
9174              * without a position diagram. */
9175             if (lastLoadGameStart == (ChessMove) 0) {
9176               gn--;
9177               lastLoadGameStart = MoveNumberOne;
9178             }
9179             break;
9180
9181           default:
9182             break;
9183         }
9184     }
9185     
9186     if (appData.debugMode)
9187       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9188
9189     if (cm == XBoardGame) {
9190         /* Skip any header junk before position diagram and/or move 1 */
9191         for (;;) {
9192             yyboardindex = forwardMostMove;
9193             cm = (ChessMove) yylex();
9194
9195             if (cm == (ChessMove) 0 ||
9196                 cm == GNUChessGame || cm == XBoardGame) {
9197                 /* Empty game; pretend end-of-file and handle later */
9198                 cm = (ChessMove) 0;
9199                 break;
9200             }
9201
9202             if (cm == MoveNumberOne || cm == PositionDiagram ||
9203                 cm == PGNTag || cm == Comment)
9204               break;
9205         }
9206     } else if (cm == GNUChessGame) {
9207         if (gameInfo.event != NULL) {
9208             free(gameInfo.event);
9209         }
9210         gameInfo.event = StrSave(yy_text);
9211     }   
9212
9213     startedFromSetupPosition = FALSE;
9214     while (cm == PGNTag) {
9215         if (appData.debugMode) 
9216           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9217         err = ParsePGNTag(yy_text, &gameInfo);
9218         if (!err) numPGNTags++;
9219
9220         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9221         if(gameInfo.variant != oldVariant) {
9222             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9223             InitPosition(TRUE);
9224             oldVariant = gameInfo.variant;
9225             if (appData.debugMode) 
9226               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9227         }
9228
9229
9230         if (gameInfo.fen != NULL) {
9231           Board initial_position;
9232           startedFromSetupPosition = TRUE;
9233           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9234             Reset(TRUE, TRUE);
9235             DisplayError(_("Bad FEN position in file"), 0);
9236             return FALSE;
9237           }
9238           CopyBoard(boards[0], initial_position);
9239           if (blackPlaysFirst) {
9240             currentMove = forwardMostMove = backwardMostMove = 1;
9241             CopyBoard(boards[1], initial_position);
9242             strcpy(moveList[0], "");
9243             strcpy(parseList[0], "");
9244             timeRemaining[0][1] = whiteTimeRemaining;
9245             timeRemaining[1][1] = blackTimeRemaining;
9246             if (commentList[0] != NULL) {
9247               commentList[1] = commentList[0];
9248               commentList[0] = NULL;
9249             }
9250           } else {
9251             currentMove = forwardMostMove = backwardMostMove = 0;
9252           }
9253           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9254           {   int i;
9255               initialRulePlies = FENrulePlies;
9256               epStatus[forwardMostMove] = FENepStatus;
9257               for( i=0; i< nrCastlingRights; i++ )
9258                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9259           }
9260           yyboardindex = forwardMostMove;
9261           free(gameInfo.fen);
9262           gameInfo.fen = NULL;
9263         }
9264
9265         yyboardindex = forwardMostMove;
9266         cm = (ChessMove) yylex();
9267
9268         /* Handle comments interspersed among the tags */
9269         while (cm == Comment) {
9270             char *p;
9271             if (appData.debugMode) 
9272               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9273             p = yy_text;
9274             if (*p == '{' || *p == '[' || *p == '(') {
9275                 p[strlen(p) - 1] = NULLCHAR;
9276                 p++;
9277             }
9278             while (*p == '\n') p++;
9279             AppendComment(currentMove, p);
9280             yyboardindex = forwardMostMove;
9281             cm = (ChessMove) yylex();
9282         }
9283     }
9284
9285     /* don't rely on existence of Event tag since if game was
9286      * pasted from clipboard the Event tag may not exist
9287      */
9288     if (numPGNTags > 0){
9289         char *tags;
9290         if (gameInfo.variant == VariantNormal) {
9291           gameInfo.variant = StringToVariant(gameInfo.event);
9292         }
9293         if (!matchMode) {
9294           if( appData.autoDisplayTags ) {
9295             tags = PGNTags(&gameInfo);
9296             TagsPopUp(tags, CmailMsg());
9297             free(tags);
9298           }
9299         }
9300     } else {
9301         /* Make something up, but don't display it now */
9302         SetGameInfo();
9303         TagsPopDown();
9304     }
9305
9306     if (cm == PositionDiagram) {
9307         int i, j;
9308         char *p;
9309         Board initial_position;
9310
9311         if (appData.debugMode)
9312           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9313
9314         if (!startedFromSetupPosition) {
9315             p = yy_text;
9316             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9317               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9318                 switch (*p) {
9319                   case '[':
9320                   case '-':
9321                   case ' ':
9322                   case '\t':
9323                   case '\n':
9324                   case '\r':
9325                     break;
9326                   default:
9327                     initial_position[i][j++] = CharToPiece(*p);
9328                     break;
9329                 }
9330             while (*p == ' ' || *p == '\t' ||
9331                    *p == '\n' || *p == '\r') p++;
9332         
9333             if (strncmp(p, "black", strlen("black"))==0)
9334               blackPlaysFirst = TRUE;
9335             else
9336               blackPlaysFirst = FALSE;
9337             startedFromSetupPosition = TRUE;
9338         
9339             CopyBoard(boards[0], initial_position);
9340             if (blackPlaysFirst) {
9341                 currentMove = forwardMostMove = backwardMostMove = 1;
9342                 CopyBoard(boards[1], initial_position);
9343                 strcpy(moveList[0], "");
9344                 strcpy(parseList[0], "");
9345                 timeRemaining[0][1] = whiteTimeRemaining;
9346                 timeRemaining[1][1] = blackTimeRemaining;
9347                 if (commentList[0] != NULL) {
9348                     commentList[1] = commentList[0];
9349                     commentList[0] = NULL;
9350                 }
9351             } else {
9352                 currentMove = forwardMostMove = backwardMostMove = 0;
9353             }
9354         }
9355         yyboardindex = forwardMostMove;
9356         cm = (ChessMove) yylex();
9357     }
9358
9359     if (first.pr == NoProc) {
9360         StartChessProgram(&first);
9361     }
9362     InitChessProgram(&first, FALSE);
9363     SendToProgram("force\n", &first);
9364     if (startedFromSetupPosition) {
9365         SendBoard(&first, forwardMostMove);
9366     if (appData.debugMode) {
9367         fprintf(debugFP, "Load Game\n");
9368     }
9369         DisplayBothClocks();
9370     }      
9371
9372     /* [HGM] server: flag to write setup moves in broadcast file as one */
9373     loadFlag = appData.suppressLoadMoves;
9374
9375     while (cm == Comment) {
9376         char *p;
9377         if (appData.debugMode) 
9378           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9379         p = yy_text;
9380         if (*p == '{' || *p == '[' || *p == '(') {
9381             p[strlen(p) - 1] = NULLCHAR;
9382             p++;
9383         }
9384         while (*p == '\n') p++;
9385         AppendComment(currentMove, p);
9386         yyboardindex = forwardMostMove;
9387         cm = (ChessMove) yylex();
9388     }
9389
9390     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9391         cm == WhiteWins || cm == BlackWins ||
9392         cm == GameIsDrawn || cm == GameUnfinished) {
9393         DisplayMessage("", _("No moves in game"));
9394         if (cmailMsgLoaded) {
9395             if (appData.debugMode)
9396               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9397             ClearHighlights();
9398             flipView = FALSE;
9399         }
9400         DrawPosition(FALSE, boards[currentMove]);
9401         DisplayBothClocks();
9402         gameMode = EditGame;
9403         ModeHighlight();
9404         gameFileFP = NULL;
9405         cmailOldMove = 0;
9406         return TRUE;
9407     }
9408
9409     // [HGM] PV info: routine tests if comment empty
9410     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9411         DisplayComment(currentMove - 1, commentList[currentMove]);
9412     }
9413     if (!matchMode && appData.timeDelay != 0) 
9414       DrawPosition(FALSE, boards[currentMove]);
9415
9416     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9417       programStats.ok_to_send = 1;
9418     }
9419
9420     /* if the first token after the PGN tags is a move
9421      * and not move number 1, retrieve it from the parser 
9422      */
9423     if (cm != MoveNumberOne)
9424         LoadGameOneMove(cm);
9425
9426     /* load the remaining moves from the file */
9427     while (LoadGameOneMove((ChessMove)0)) {
9428       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9429       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9430     }
9431
9432     /* rewind to the start of the game */
9433     currentMove = backwardMostMove;
9434
9435     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9436
9437     if (oldGameMode == AnalyzeFile ||
9438         oldGameMode == AnalyzeMode) {
9439       AnalyzeFileEvent();
9440     }
9441
9442     if (matchMode || appData.timeDelay == 0) {
9443       ToEndEvent();
9444       gameMode = EditGame;
9445       ModeHighlight();
9446     } else if (appData.timeDelay > 0) {
9447       AutoPlayGameLoop();
9448     }
9449
9450     if (appData.debugMode) 
9451         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9452
9453     loadFlag = 0; /* [HGM] true game starts */
9454     return TRUE;
9455 }
9456
9457 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9458 int
9459 ReloadPosition(offset)
9460      int offset;
9461 {
9462     int positionNumber = lastLoadPositionNumber + offset;
9463     if (lastLoadPositionFP == NULL) {
9464         DisplayError(_("No position has been loaded yet"), 0);
9465         return FALSE;
9466     }
9467     if (positionNumber <= 0) {
9468         DisplayError(_("Can't back up any further"), 0);
9469         return FALSE;
9470     }
9471     return LoadPosition(lastLoadPositionFP, positionNumber,
9472                         lastLoadPositionTitle);
9473 }
9474
9475 /* Load the nth position from the given file */
9476 int
9477 LoadPositionFromFile(filename, n, title)
9478      char *filename;
9479      int n;
9480      char *title;
9481 {
9482     FILE *f;
9483     char buf[MSG_SIZ];
9484
9485     if (strcmp(filename, "-") == 0) {
9486         return LoadPosition(stdin, n, "stdin");
9487     } else {
9488         f = fopen(filename, "rb");
9489         if (f == NULL) {
9490             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9491             DisplayError(buf, errno);
9492             return FALSE;
9493         } else {
9494             return LoadPosition(f, n, title);
9495         }
9496     }
9497 }
9498
9499 /* Load the nth position from the given open file, and close it */
9500 int
9501 LoadPosition(f, positionNumber, title)
9502      FILE *f;
9503      int positionNumber;
9504      char *title;
9505 {
9506     char *p, line[MSG_SIZ];
9507     Board initial_position;
9508     int i, j, fenMode, pn;
9509     
9510     if (gameMode == Training )
9511         SetTrainingModeOff();
9512
9513     if (gameMode != BeginningOfGame) {
9514         Reset(FALSE, TRUE);
9515     }
9516     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9517         fclose(lastLoadPositionFP);
9518     }
9519     if (positionNumber == 0) positionNumber = 1;
9520     lastLoadPositionFP = f;
9521     lastLoadPositionNumber = positionNumber;
9522     strcpy(lastLoadPositionTitle, title);
9523     if (first.pr == NoProc) {
9524       StartChessProgram(&first);
9525       InitChessProgram(&first, FALSE);
9526     }    
9527     pn = positionNumber;
9528     if (positionNumber < 0) {
9529         /* Negative position number means to seek to that byte offset */
9530         if (fseek(f, -positionNumber, 0) == -1) {
9531             DisplayError(_("Can't seek on position file"), 0);
9532             return FALSE;
9533         };
9534         pn = 1;
9535     } else {
9536         if (fseek(f, 0, 0) == -1) {
9537             if (f == lastLoadPositionFP ?
9538                 positionNumber == lastLoadPositionNumber + 1 :
9539                 positionNumber == 1) {
9540                 pn = 1;
9541             } else {
9542                 DisplayError(_("Can't seek on position file"), 0);
9543                 return FALSE;
9544             }
9545         }
9546     }
9547     /* See if this file is FEN or old-style xboard */
9548     if (fgets(line, MSG_SIZ, f) == NULL) {
9549         DisplayError(_("Position not found in file"), 0);
9550         return FALSE;
9551     }
9552     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9553     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9554
9555     if (pn >= 2) {
9556         if (fenMode || line[0] == '#') pn--;
9557         while (pn > 0) {
9558             /* skip positions before number pn */
9559             if (fgets(line, MSG_SIZ, f) == NULL) {
9560                 Reset(TRUE, TRUE);
9561                 DisplayError(_("Position not found in file"), 0);
9562                 return FALSE;
9563             }
9564             if (fenMode || line[0] == '#') pn--;
9565         }
9566     }
9567
9568     if (fenMode) {
9569         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9570             DisplayError(_("Bad FEN position in file"), 0);
9571             return FALSE;
9572         }
9573     } else {
9574         (void) fgets(line, MSG_SIZ, f);
9575         (void) fgets(line, MSG_SIZ, f);
9576     
9577         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9578             (void) fgets(line, MSG_SIZ, f);
9579             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9580                 if (*p == ' ')
9581                   continue;
9582                 initial_position[i][j++] = CharToPiece(*p);
9583             }
9584         }
9585     
9586         blackPlaysFirst = FALSE;
9587         if (!feof(f)) {
9588             (void) fgets(line, MSG_SIZ, f);
9589             if (strncmp(line, "black", strlen("black"))==0)
9590               blackPlaysFirst = TRUE;
9591         }
9592     }
9593     startedFromSetupPosition = TRUE;
9594     
9595     SendToProgram("force\n", &first);
9596     CopyBoard(boards[0], initial_position);
9597     if (blackPlaysFirst) {
9598         currentMove = forwardMostMove = backwardMostMove = 1;
9599         strcpy(moveList[0], "");
9600         strcpy(parseList[0], "");
9601         CopyBoard(boards[1], initial_position);
9602         DisplayMessage("", _("Black to play"));
9603     } else {
9604         currentMove = forwardMostMove = backwardMostMove = 0;
9605         DisplayMessage("", _("White to play"));
9606     }
9607           /* [HGM] copy FEN attributes as well */
9608           {   int i;
9609               initialRulePlies = FENrulePlies;
9610               epStatus[forwardMostMove] = FENepStatus;
9611               for( i=0; i< nrCastlingRights; i++ )
9612                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9613           }
9614     SendBoard(&first, forwardMostMove);
9615     if (appData.debugMode) {
9616 int i, j;
9617   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9618   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9619         fprintf(debugFP, "Load Position\n");
9620     }
9621
9622     if (positionNumber > 1) {
9623         sprintf(line, "%s %d", title, positionNumber);
9624         DisplayTitle(line);
9625     } else {
9626         DisplayTitle(title);
9627     }
9628     gameMode = EditGame;
9629     ModeHighlight();
9630     ResetClocks();
9631     timeRemaining[0][1] = whiteTimeRemaining;
9632     timeRemaining[1][1] = blackTimeRemaining;
9633     DrawPosition(FALSE, boards[currentMove]);
9634    
9635     return TRUE;
9636 }
9637
9638
9639 void
9640 CopyPlayerNameIntoFileName(dest, src)
9641      char **dest, *src;
9642 {
9643     while (*src != NULLCHAR && *src != ',') {
9644         if (*src == ' ') {
9645             *(*dest)++ = '_';
9646             src++;
9647         } else {
9648             *(*dest)++ = *src++;
9649         }
9650     }
9651 }
9652
9653 char *DefaultFileName(ext)
9654      char *ext;
9655 {
9656     static char def[MSG_SIZ];
9657     char *p;
9658
9659     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9660         p = def;
9661         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9662         *p++ = '-';
9663         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9664         *p++ = '.';
9665         strcpy(p, ext);
9666     } else {
9667         def[0] = NULLCHAR;
9668     }
9669     return def;
9670 }
9671
9672 /* Save the current game to the given file */
9673 int
9674 SaveGameToFile(filename, append)
9675      char *filename;
9676      int append;
9677 {
9678     FILE *f;
9679     char buf[MSG_SIZ];
9680
9681     if (strcmp(filename, "-") == 0) {
9682         return SaveGame(stdout, 0, NULL);
9683     } else {
9684         f = fopen(filename, append ? "a" : "w");
9685         if (f == NULL) {
9686             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9687             DisplayError(buf, errno);
9688             return FALSE;
9689         } else {
9690             return SaveGame(f, 0, NULL);
9691         }
9692     }
9693 }
9694
9695 char *
9696 SavePart(str)
9697      char *str;
9698 {
9699     static char buf[MSG_SIZ];
9700     char *p;
9701     
9702     p = strchr(str, ' ');
9703     if (p == NULL) return str;
9704     strncpy(buf, str, p - str);
9705     buf[p - str] = NULLCHAR;
9706     return buf;
9707 }
9708
9709 #define PGN_MAX_LINE 75
9710
9711 #define PGN_SIDE_WHITE  0
9712 #define PGN_SIDE_BLACK  1
9713
9714 /* [AS] */
9715 static int FindFirstMoveOutOfBook( int side )
9716 {
9717     int result = -1;
9718
9719     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9720         int index = backwardMostMove;
9721         int has_book_hit = 0;
9722
9723         if( (index % 2) != side ) {
9724             index++;
9725         }
9726
9727         while( index < forwardMostMove ) {
9728             /* Check to see if engine is in book */
9729             int depth = pvInfoList[index].depth;
9730             int score = pvInfoList[index].score;
9731             int in_book = 0;
9732
9733             if( depth <= 2 ) {
9734                 in_book = 1;
9735             }
9736             else if( score == 0 && depth == 63 ) {
9737                 in_book = 1; /* Zappa */
9738             }
9739             else if( score == 2 && depth == 99 ) {
9740                 in_book = 1; /* Abrok */
9741             }
9742
9743             has_book_hit += in_book;
9744
9745             if( ! in_book ) {
9746                 result = index;
9747
9748                 break;
9749             }
9750
9751             index += 2;
9752         }
9753     }
9754
9755     return result;
9756 }
9757
9758 /* [AS] */
9759 void GetOutOfBookInfo( char * buf )
9760 {
9761     int oob[2];
9762     int i;
9763     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9764
9765     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9766     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9767
9768     *buf = '\0';
9769
9770     if( oob[0] >= 0 || oob[1] >= 0 ) {
9771         for( i=0; i<2; i++ ) {
9772             int idx = oob[i];
9773
9774             if( idx >= 0 ) {
9775                 if( i > 0 && oob[0] >= 0 ) {
9776                     strcat( buf, "   " );
9777                 }
9778
9779                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9780                 sprintf( buf+strlen(buf), "%s%.2f", 
9781                     pvInfoList[idx].score >= 0 ? "+" : "",
9782                     pvInfoList[idx].score / 100.0 );
9783             }
9784         }
9785     }
9786 }
9787
9788 /* Save game in PGN style and close the file */
9789 int
9790 SaveGamePGN(f)
9791      FILE *f;
9792 {
9793     int i, offset, linelen, newblock;
9794     time_t tm;
9795 //    char *movetext;
9796     char numtext[32];
9797     int movelen, numlen, blank;
9798     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9799
9800     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9801     
9802     tm = time((time_t *) NULL);
9803     
9804     PrintPGNTags(f, &gameInfo);
9805     
9806     if (backwardMostMove > 0 || startedFromSetupPosition) {
9807         char *fen = PositionToFEN(backwardMostMove, NULL);
9808         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9809         fprintf(f, "\n{--------------\n");
9810         PrintPosition(f, backwardMostMove);
9811         fprintf(f, "--------------}\n");
9812         free(fen);
9813     }
9814     else {
9815         /* [AS] Out of book annotation */
9816         if( appData.saveOutOfBookInfo ) {
9817             char buf[64];
9818
9819             GetOutOfBookInfo( buf );
9820
9821             if( buf[0] != '\0' ) {
9822                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9823             }
9824         }
9825
9826         fprintf(f, "\n");
9827     }
9828
9829     i = backwardMostMove;
9830     linelen = 0;
9831     newblock = TRUE;
9832
9833     while (i < forwardMostMove) {
9834         /* Print comments preceding this move */
9835         if (commentList[i] != NULL) {
9836             if (linelen > 0) fprintf(f, "\n");
9837             fprintf(f, "{\n%s}\n", commentList[i]);
9838             linelen = 0;
9839             newblock = TRUE;
9840         }
9841
9842         /* Format move number */
9843         if ((i % 2) == 0) {
9844             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9845         } else {
9846             if (newblock) {
9847                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9848             } else {
9849                 numtext[0] = NULLCHAR;
9850             }
9851         }
9852         numlen = strlen(numtext);
9853         newblock = FALSE;
9854
9855         /* Print move number */
9856         blank = linelen > 0 && numlen > 0;
9857         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9858             fprintf(f, "\n");
9859             linelen = 0;
9860             blank = 0;
9861         }
9862         if (blank) {
9863             fprintf(f, " ");
9864             linelen++;
9865         }
9866         fprintf(f, "%s", numtext);
9867         linelen += numlen;
9868
9869         /* Get move */
9870         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9871         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9872
9873         /* Print move */
9874         blank = linelen > 0 && movelen > 0;
9875         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9876             fprintf(f, "\n");
9877             linelen = 0;
9878             blank = 0;
9879         }
9880         if (blank) {
9881             fprintf(f, " ");
9882             linelen++;
9883         }
9884         fprintf(f, "%s", move_buffer);
9885         linelen += movelen;
9886
9887         /* [AS] Add PV info if present */
9888         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9889             /* [HGM] add time */
9890             char buf[MSG_SIZ]; int seconds = 0;
9891
9892             if(i >= backwardMostMove) {
9893                 if(WhiteOnMove(i))
9894                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9895                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9896                 else
9897                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9898                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9899             }
9900             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9901
9902             if( seconds <= 0) buf[0] = 0; else
9903             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9904                 seconds = (seconds + 4)/10; // round to full seconds
9905                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9906                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9907             }
9908
9909             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9910                 pvInfoList[i].score >= 0 ? "+" : "",
9911                 pvInfoList[i].score / 100.0,
9912                 pvInfoList[i].depth,
9913                 buf );
9914
9915             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9916
9917             /* Print score/depth */
9918             blank = linelen > 0 && movelen > 0;
9919             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9920                 fprintf(f, "\n");
9921                 linelen = 0;
9922                 blank = 0;
9923             }
9924             if (blank) {
9925                 fprintf(f, " ");
9926                 linelen++;
9927             }
9928             fprintf(f, "%s", move_buffer);
9929             linelen += movelen;
9930         }
9931
9932         i++;
9933     }
9934     
9935     /* Start a new line */
9936     if (linelen > 0) fprintf(f, "\n");
9937
9938     /* Print comments after last move */
9939     if (commentList[i] != NULL) {
9940         fprintf(f, "{\n%s}\n", commentList[i]);
9941     }
9942
9943     /* Print result */
9944     if (gameInfo.resultDetails != NULL &&
9945         gameInfo.resultDetails[0] != NULLCHAR) {
9946         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9947                 PGNResult(gameInfo.result));
9948     } else {
9949         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9950     }
9951
9952     fclose(f);
9953     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9954     return TRUE;
9955 }
9956
9957 /* Save game in old style and close the file */
9958 int
9959 SaveGameOldStyle(f)
9960      FILE *f;
9961 {
9962     int i, offset;
9963     time_t tm;
9964     
9965     tm = time((time_t *) NULL);
9966     
9967     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9968     PrintOpponents(f);
9969     
9970     if (backwardMostMove > 0 || startedFromSetupPosition) {
9971         fprintf(f, "\n[--------------\n");
9972         PrintPosition(f, backwardMostMove);
9973         fprintf(f, "--------------]\n");
9974     } else {
9975         fprintf(f, "\n");
9976     }
9977
9978     i = backwardMostMove;
9979     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9980
9981     while (i < forwardMostMove) {
9982         if (commentList[i] != NULL) {
9983             fprintf(f, "[%s]\n", commentList[i]);
9984         }
9985
9986         if ((i % 2) == 1) {
9987             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9988             i++;
9989         } else {
9990             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9991             i++;
9992             if (commentList[i] != NULL) {
9993                 fprintf(f, "\n");
9994                 continue;
9995             }
9996             if (i >= forwardMostMove) {
9997                 fprintf(f, "\n");
9998                 break;
9999             }
10000             fprintf(f, "%s\n", parseList[i]);
10001             i++;
10002         }
10003     }
10004     
10005     if (commentList[i] != NULL) {
10006         fprintf(f, "[%s]\n", commentList[i]);
10007     }
10008
10009     /* This isn't really the old style, but it's close enough */
10010     if (gameInfo.resultDetails != NULL &&
10011         gameInfo.resultDetails[0] != NULLCHAR) {
10012         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10013                 gameInfo.resultDetails);
10014     } else {
10015         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10016     }
10017
10018     fclose(f);
10019     return TRUE;
10020 }
10021
10022 /* Save the current game to open file f and close the file */
10023 int
10024 SaveGame(f, dummy, dummy2)
10025      FILE *f;
10026      int dummy;
10027      char *dummy2;
10028 {
10029     if (gameMode == EditPosition) EditPositionDone();
10030     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10031     if (appData.oldSaveStyle)
10032       return SaveGameOldStyle(f);
10033     else
10034       return SaveGamePGN(f);
10035 }
10036
10037 /* Save the current position to the given file */
10038 int
10039 SavePositionToFile(filename)
10040      char *filename;
10041 {
10042     FILE *f;
10043     char buf[MSG_SIZ];
10044
10045     if (strcmp(filename, "-") == 0) {
10046         return SavePosition(stdout, 0, NULL);
10047     } else {
10048         f = fopen(filename, "a");
10049         if (f == NULL) {
10050             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10051             DisplayError(buf, errno);
10052             return FALSE;
10053         } else {
10054             SavePosition(f, 0, NULL);
10055             return TRUE;
10056         }
10057     }
10058 }
10059
10060 /* Save the current position to the given open file and close the file */
10061 int
10062 SavePosition(f, dummy, dummy2)
10063      FILE *f;
10064      int dummy;
10065      char *dummy2;
10066 {
10067     time_t tm;
10068     char *fen;
10069     
10070     if (appData.oldSaveStyle) {
10071         tm = time((time_t *) NULL);
10072     
10073         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10074         PrintOpponents(f);
10075         fprintf(f, "[--------------\n");
10076         PrintPosition(f, currentMove);
10077         fprintf(f, "--------------]\n");
10078     } else {
10079         fen = PositionToFEN(currentMove, NULL);
10080         fprintf(f, "%s\n", fen);
10081         free(fen);
10082     }
10083     fclose(f);
10084     return TRUE;
10085 }
10086
10087 void
10088 ReloadCmailMsgEvent(unregister)
10089      int unregister;
10090 {
10091 #if !WIN32
10092     static char *inFilename = NULL;
10093     static char *outFilename;
10094     int i;
10095     struct stat inbuf, outbuf;
10096     int status;
10097     
10098     /* Any registered moves are unregistered if unregister is set, */
10099     /* i.e. invoked by the signal handler */
10100     if (unregister) {
10101         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10102             cmailMoveRegistered[i] = FALSE;
10103             if (cmailCommentList[i] != NULL) {
10104                 free(cmailCommentList[i]);
10105                 cmailCommentList[i] = NULL;
10106             }
10107         }
10108         nCmailMovesRegistered = 0;
10109     }
10110
10111     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10112         cmailResult[i] = CMAIL_NOT_RESULT;
10113     }
10114     nCmailResults = 0;
10115
10116     if (inFilename == NULL) {
10117         /* Because the filenames are static they only get malloced once  */
10118         /* and they never get freed                                      */
10119         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10120         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10121
10122         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10123         sprintf(outFilename, "%s.out", appData.cmailGameName);
10124     }
10125     
10126     status = stat(outFilename, &outbuf);
10127     if (status < 0) {
10128         cmailMailedMove = FALSE;
10129     } else {
10130         status = stat(inFilename, &inbuf);
10131         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10132     }
10133     
10134     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10135        counts the games, notes how each one terminated, etc.
10136        
10137        It would be nice to remove this kludge and instead gather all
10138        the information while building the game list.  (And to keep it
10139        in the game list nodes instead of having a bunch of fixed-size
10140        parallel arrays.)  Note this will require getting each game's
10141        termination from the PGN tags, as the game list builder does
10142        not process the game moves.  --mann
10143        */
10144     cmailMsgLoaded = TRUE;
10145     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10146     
10147     /* Load first game in the file or popup game menu */
10148     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10149
10150 #endif /* !WIN32 */
10151     return;
10152 }
10153
10154 int
10155 RegisterMove()
10156 {
10157     FILE *f;
10158     char string[MSG_SIZ];
10159
10160     if (   cmailMailedMove
10161         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10162         return TRUE;            /* Allow free viewing  */
10163     }
10164
10165     /* Unregister move to ensure that we don't leave RegisterMove        */
10166     /* with the move registered when the conditions for registering no   */
10167     /* longer hold                                                       */
10168     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10169         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10170         nCmailMovesRegistered --;
10171
10172         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10173           {
10174               free(cmailCommentList[lastLoadGameNumber - 1]);
10175               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10176           }
10177     }
10178
10179     if (cmailOldMove == -1) {
10180         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10181         return FALSE;
10182     }
10183
10184     if (currentMove > cmailOldMove + 1) {
10185         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10186         return FALSE;
10187     }
10188
10189     if (currentMove < cmailOldMove) {
10190         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10191         return FALSE;
10192     }
10193
10194     if (forwardMostMove > currentMove) {
10195         /* Silently truncate extra moves */
10196         TruncateGame();
10197     }
10198
10199     if (   (currentMove == cmailOldMove + 1)
10200         || (   (currentMove == cmailOldMove)
10201             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10202                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10203         if (gameInfo.result != GameUnfinished) {
10204             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10205         }
10206
10207         if (commentList[currentMove] != NULL) {
10208             cmailCommentList[lastLoadGameNumber - 1]
10209               = StrSave(commentList[currentMove]);
10210         }
10211         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10212
10213         if (appData.debugMode)
10214           fprintf(debugFP, "Saving %s for game %d\n",
10215                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10216
10217         sprintf(string,
10218                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10219         
10220         f = fopen(string, "w");
10221         if (appData.oldSaveStyle) {
10222             SaveGameOldStyle(f); /* also closes the file */
10223             
10224             sprintf(string, "%s.pos.out", appData.cmailGameName);
10225             f = fopen(string, "w");
10226             SavePosition(f, 0, NULL); /* also closes the file */
10227         } else {
10228             fprintf(f, "{--------------\n");
10229             PrintPosition(f, currentMove);
10230             fprintf(f, "--------------}\n\n");
10231             
10232             SaveGame(f, 0, NULL); /* also closes the file*/
10233         }
10234         
10235         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10236         nCmailMovesRegistered ++;
10237     } else if (nCmailGames == 1) {
10238         DisplayError(_("You have not made a move yet"), 0);
10239         return FALSE;
10240     }
10241
10242     return TRUE;
10243 }
10244
10245 void
10246 MailMoveEvent()
10247 {
10248 #if !WIN32
10249     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10250     FILE *commandOutput;
10251     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10252     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10253     int nBuffers;
10254     int i;
10255     int archived;
10256     char *arcDir;
10257
10258     if (! cmailMsgLoaded) {
10259         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10260         return;
10261     }
10262
10263     if (nCmailGames == nCmailResults) {
10264         DisplayError(_("No unfinished games"), 0);
10265         return;
10266     }
10267
10268 #if CMAIL_PROHIBIT_REMAIL
10269     if (cmailMailedMove) {
10270         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);
10271         DisplayError(msg, 0);
10272         return;
10273     }
10274 #endif
10275
10276     if (! (cmailMailedMove || RegisterMove())) return;
10277     
10278     if (   cmailMailedMove
10279         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10280         sprintf(string, partCommandString,
10281                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10282         commandOutput = popen(string, "r");
10283
10284         if (commandOutput == NULL) {
10285             DisplayError(_("Failed to invoke cmail"), 0);
10286         } else {
10287             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10288                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10289             }
10290             if (nBuffers > 1) {
10291                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10292                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10293                 nBytes = MSG_SIZ - 1;
10294             } else {
10295                 (void) memcpy(msg, buffer, nBytes);
10296             }
10297             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10298
10299             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10300                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10301
10302                 archived = TRUE;
10303                 for (i = 0; i < nCmailGames; i ++) {
10304                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10305                         archived = FALSE;
10306                     }
10307                 }
10308                 if (   archived
10309                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10310                         != NULL)) {
10311                     sprintf(buffer, "%s/%s.%s.archive",
10312                             arcDir,
10313                             appData.cmailGameName,
10314                             gameInfo.date);
10315                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10316                     cmailMsgLoaded = FALSE;
10317                 }
10318             }
10319
10320             DisplayInformation(msg);
10321             pclose(commandOutput);
10322         }
10323     } else {
10324         if ((*cmailMsg) != '\0') {
10325             DisplayInformation(cmailMsg);
10326         }
10327     }
10328
10329     return;
10330 #endif /* !WIN32 */
10331 }
10332
10333 char *
10334 CmailMsg()
10335 {
10336 #if WIN32
10337     return NULL;
10338 #else
10339     int  prependComma = 0;
10340     char number[5];
10341     char string[MSG_SIZ];       /* Space for game-list */
10342     int  i;
10343     
10344     if (!cmailMsgLoaded) return "";
10345
10346     if (cmailMailedMove) {
10347         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10348     } else {
10349         /* Create a list of games left */
10350         sprintf(string, "[");
10351         for (i = 0; i < nCmailGames; i ++) {
10352             if (! (   cmailMoveRegistered[i]
10353                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10354                 if (prependComma) {
10355                     sprintf(number, ",%d", i + 1);
10356                 } else {
10357                     sprintf(number, "%d", i + 1);
10358                     prependComma = 1;
10359                 }
10360                 
10361                 strcat(string, number);
10362             }
10363         }
10364         strcat(string, "]");
10365
10366         if (nCmailMovesRegistered + nCmailResults == 0) {
10367             switch (nCmailGames) {
10368               case 1:
10369                 sprintf(cmailMsg,
10370                         _("Still need to make move for game\n"));
10371                 break;
10372                 
10373               case 2:
10374                 sprintf(cmailMsg,
10375                         _("Still need to make moves for both games\n"));
10376                 break;
10377                 
10378               default:
10379                 sprintf(cmailMsg,
10380                         _("Still need to make moves for all %d games\n"),
10381                         nCmailGames);
10382                 break;
10383             }
10384         } else {
10385             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10386               case 1:
10387                 sprintf(cmailMsg,
10388                         _("Still need to make a move for game %s\n"),
10389                         string);
10390                 break;
10391                 
10392               case 0:
10393                 if (nCmailResults == nCmailGames) {
10394                     sprintf(cmailMsg, _("No unfinished games\n"));
10395                 } else {
10396                     sprintf(cmailMsg, _("Ready to send mail\n"));
10397                 }
10398                 break;
10399                 
10400               default:
10401                 sprintf(cmailMsg,
10402                         _("Still need to make moves for games %s\n"),
10403                         string);
10404             }
10405         }
10406     }
10407     return cmailMsg;
10408 #endif /* WIN32 */
10409 }
10410
10411 void
10412 ResetGameEvent()
10413 {
10414     if (gameMode == Training)
10415       SetTrainingModeOff();
10416
10417     Reset(TRUE, TRUE);
10418     cmailMsgLoaded = FALSE;
10419     if (appData.icsActive) {
10420       SendToICS(ics_prefix);
10421       SendToICS("refresh\n");
10422     }
10423 }
10424
10425 void
10426 ExitEvent(status)
10427      int status;
10428 {
10429     exiting++;
10430     if (exiting > 2) {
10431       /* Give up on clean exit */
10432       exit(status);
10433     }
10434     if (exiting > 1) {
10435       /* Keep trying for clean exit */
10436       return;
10437     }
10438
10439     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10440
10441     if (telnetISR != NULL) {
10442       RemoveInputSource(telnetISR);
10443     }
10444     if (icsPR != NoProc) {
10445       DestroyChildProcess(icsPR, TRUE);
10446     }
10447
10448     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10449     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10450
10451     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10452     /* make sure this other one finishes before killing it!                  */
10453     if(endingGame) { int count = 0;
10454         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10455         while(endingGame && count++ < 10) DoSleep(1);
10456         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10457     }
10458
10459     /* Kill off chess programs */
10460     if (first.pr != NoProc) {
10461         ExitAnalyzeMode();
10462         
10463         DoSleep( appData.delayBeforeQuit );
10464         SendToProgram("quit\n", &first);
10465         DoSleep( appData.delayAfterQuit );
10466         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10467     }
10468     if (second.pr != NoProc) {
10469         DoSleep( appData.delayBeforeQuit );
10470         SendToProgram("quit\n", &second);
10471         DoSleep( appData.delayAfterQuit );
10472         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10473     }
10474     if (first.isr != NULL) {
10475         RemoveInputSource(first.isr);
10476     }
10477     if (second.isr != NULL) {
10478         RemoveInputSource(second.isr);
10479     }
10480
10481     ShutDownFrontEnd();
10482     exit(status);
10483 }
10484
10485 void
10486 PauseEvent()
10487 {
10488     if (appData.debugMode)
10489         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10490     if (pausing) {
10491         pausing = FALSE;
10492         ModeHighlight();
10493         if (gameMode == MachinePlaysWhite ||
10494             gameMode == MachinePlaysBlack) {
10495             StartClocks();
10496         } else {
10497             DisplayBothClocks();
10498         }
10499         if (gameMode == PlayFromGameFile) {
10500             if (appData.timeDelay >= 0) 
10501                 AutoPlayGameLoop();
10502         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10503             Reset(FALSE, TRUE);
10504             SendToICS(ics_prefix);
10505             SendToICS("refresh\n");
10506         } else if (currentMove < forwardMostMove) {
10507             ForwardInner(forwardMostMove);
10508         }
10509         pauseExamInvalid = FALSE;
10510     } else {
10511         switch (gameMode) {
10512           default:
10513             return;
10514           case IcsExamining:
10515             pauseExamForwardMostMove = forwardMostMove;
10516             pauseExamInvalid = FALSE;
10517             /* fall through */
10518           case IcsObserving:
10519           case IcsPlayingWhite:
10520           case IcsPlayingBlack:
10521             pausing = TRUE;
10522             ModeHighlight();
10523             return;
10524           case PlayFromGameFile:
10525             (void) StopLoadGameTimer();
10526             pausing = TRUE;
10527             ModeHighlight();
10528             break;
10529           case BeginningOfGame:
10530             if (appData.icsActive) return;
10531             /* else fall through */
10532           case MachinePlaysWhite:
10533           case MachinePlaysBlack:
10534           case TwoMachinesPlay:
10535             if (forwardMostMove == 0)
10536               return;           /* don't pause if no one has moved */
10537             if ((gameMode == MachinePlaysWhite &&
10538                  !WhiteOnMove(forwardMostMove)) ||
10539                 (gameMode == MachinePlaysBlack &&
10540                  WhiteOnMove(forwardMostMove))) {
10541                 StopClocks();
10542             }
10543             pausing = TRUE;
10544             ModeHighlight();
10545             break;
10546         }
10547     }
10548 }
10549
10550 void
10551 EditCommentEvent()
10552 {
10553     char title[MSG_SIZ];
10554
10555     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10556         strcpy(title, _("Edit comment"));
10557     } else {
10558         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10559                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10560                 parseList[currentMove - 1]);
10561     }
10562
10563     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10564 }
10565
10566
10567 void
10568 EditTagsEvent()
10569 {
10570     char *tags = PGNTags(&gameInfo);
10571     EditTagsPopUp(tags);
10572     free(tags);
10573 }
10574
10575 void
10576 AnalyzeModeEvent()
10577 {
10578     if (appData.noChessProgram || gameMode == AnalyzeMode)
10579       return;
10580
10581     if (gameMode != AnalyzeFile) {
10582         if (!appData.icsEngineAnalyze) {
10583                EditGameEvent();
10584                if (gameMode != EditGame) return;
10585         }
10586         ResurrectChessProgram();
10587         SendToProgram("analyze\n", &first);
10588         first.analyzing = TRUE;
10589         /*first.maybeThinking = TRUE;*/
10590         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10591         EngineOutputPopUp();
10592     }
10593     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10594     pausing = FALSE;
10595     ModeHighlight();
10596     SetGameInfo();
10597
10598     StartAnalysisClock();
10599     GetTimeMark(&lastNodeCountTime);
10600     lastNodeCount = 0;
10601 }
10602
10603 void
10604 AnalyzeFileEvent()
10605 {
10606     if (appData.noChessProgram || gameMode == AnalyzeFile)
10607       return;
10608
10609     if (gameMode != AnalyzeMode) {
10610         EditGameEvent();
10611         if (gameMode != EditGame) return;
10612         ResurrectChessProgram();
10613         SendToProgram("analyze\n", &first);
10614         first.analyzing = TRUE;
10615         /*first.maybeThinking = TRUE;*/
10616         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10617         EngineOutputPopUp();
10618     }
10619     gameMode = AnalyzeFile;
10620     pausing = FALSE;
10621     ModeHighlight();
10622     SetGameInfo();
10623
10624     StartAnalysisClock();
10625     GetTimeMark(&lastNodeCountTime);
10626     lastNodeCount = 0;
10627 }
10628
10629 void
10630 MachineWhiteEvent()
10631 {
10632     char buf[MSG_SIZ];
10633     char *bookHit = NULL;
10634
10635     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10636       return;
10637
10638
10639     if (gameMode == PlayFromGameFile || 
10640         gameMode == TwoMachinesPlay  || 
10641         gameMode == Training         || 
10642         gameMode == AnalyzeMode      || 
10643         gameMode == EndOfGame)
10644         EditGameEvent();
10645
10646     if (gameMode == EditPosition) 
10647         EditPositionDone();
10648
10649     if (!WhiteOnMove(currentMove)) {
10650         DisplayError(_("It is not White's turn"), 0);
10651         return;
10652     }
10653   
10654     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10655       ExitAnalyzeMode();
10656
10657     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10658         gameMode == AnalyzeFile)
10659         TruncateGame();
10660
10661     ResurrectChessProgram();    /* in case it isn't running */
10662     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10663         gameMode = MachinePlaysWhite;
10664         ResetClocks();
10665     } else
10666     gameMode = MachinePlaysWhite;
10667     pausing = FALSE;
10668     ModeHighlight();
10669     SetGameInfo();
10670     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10671     DisplayTitle(buf);
10672     if (first.sendName) {
10673       sprintf(buf, "name %s\n", gameInfo.black);
10674       SendToProgram(buf, &first);
10675     }
10676     if (first.sendTime) {
10677       if (first.useColors) {
10678         SendToProgram("black\n", &first); /*gnu kludge*/
10679       }
10680       SendTimeRemaining(&first, TRUE);
10681     }
10682     if (first.useColors) {
10683       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10684     }
10685     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10686     SetMachineThinkingEnables();
10687     first.maybeThinking = TRUE;
10688     StartClocks();
10689     firstMove = FALSE;
10690
10691     if (appData.autoFlipView && !flipView) {
10692       flipView = !flipView;
10693       DrawPosition(FALSE, NULL);
10694       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10695     }
10696
10697     if(bookHit) { // [HGM] book: simulate book reply
10698         static char bookMove[MSG_SIZ]; // a bit generous?
10699
10700         programStats.nodes = programStats.depth = programStats.time = 
10701         programStats.score = programStats.got_only_move = 0;
10702         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10703
10704         strcpy(bookMove, "move ");
10705         strcat(bookMove, bookHit);
10706         HandleMachineMove(bookMove, &first);
10707     }
10708 }
10709
10710 void
10711 MachineBlackEvent()
10712 {
10713     char buf[MSG_SIZ];
10714    char *bookHit = NULL;
10715
10716     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10717         return;
10718
10719
10720     if (gameMode == PlayFromGameFile || 
10721         gameMode == TwoMachinesPlay  || 
10722         gameMode == Training         || 
10723         gameMode == AnalyzeMode      || 
10724         gameMode == EndOfGame)
10725         EditGameEvent();
10726
10727     if (gameMode == EditPosition) 
10728         EditPositionDone();
10729
10730     if (WhiteOnMove(currentMove)) {
10731         DisplayError(_("It is not Black's turn"), 0);
10732         return;
10733     }
10734     
10735     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10736       ExitAnalyzeMode();
10737
10738     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10739         gameMode == AnalyzeFile)
10740         TruncateGame();
10741
10742     ResurrectChessProgram();    /* in case it isn't running */
10743     gameMode = MachinePlaysBlack;
10744     pausing = FALSE;
10745     ModeHighlight();
10746     SetGameInfo();
10747     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10748     DisplayTitle(buf);
10749     if (first.sendName) {
10750       sprintf(buf, "name %s\n", gameInfo.white);
10751       SendToProgram(buf, &first);
10752     }
10753     if (first.sendTime) {
10754       if (first.useColors) {
10755         SendToProgram("white\n", &first); /*gnu kludge*/
10756       }
10757       SendTimeRemaining(&first, FALSE);
10758     }
10759     if (first.useColors) {
10760       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10761     }
10762     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10763     SetMachineThinkingEnables();
10764     first.maybeThinking = TRUE;
10765     StartClocks();
10766
10767     if (appData.autoFlipView && flipView) {
10768       flipView = !flipView;
10769       DrawPosition(FALSE, NULL);
10770       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10771     }
10772     if(bookHit) { // [HGM] book: simulate book reply
10773         static char bookMove[MSG_SIZ]; // a bit generous?
10774
10775         programStats.nodes = programStats.depth = programStats.time = 
10776         programStats.score = programStats.got_only_move = 0;
10777         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10778
10779         strcpy(bookMove, "move ");
10780         strcat(bookMove, bookHit);
10781         HandleMachineMove(bookMove, &first);
10782     }
10783 }
10784
10785
10786 void
10787 DisplayTwoMachinesTitle()
10788 {
10789     char buf[MSG_SIZ];
10790     if (appData.matchGames > 0) {
10791         if (first.twoMachinesColor[0] == 'w') {
10792             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10793                     gameInfo.white, gameInfo.black,
10794                     first.matchWins, second.matchWins,
10795                     matchGame - 1 - (first.matchWins + second.matchWins));
10796         } else {
10797             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10798                     gameInfo.white, gameInfo.black,
10799                     second.matchWins, first.matchWins,
10800                     matchGame - 1 - (first.matchWins + second.matchWins));
10801         }
10802     } else {
10803         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10804     }
10805     DisplayTitle(buf);
10806 }
10807
10808 void
10809 TwoMachinesEvent P((void))
10810 {
10811     int i;
10812     char buf[MSG_SIZ];
10813     ChessProgramState *onmove;
10814     char *bookHit = NULL;
10815     
10816     if (appData.noChessProgram) return;
10817
10818     switch (gameMode) {
10819       case TwoMachinesPlay:
10820         return;
10821       case MachinePlaysWhite:
10822       case MachinePlaysBlack:
10823         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10824             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10825             return;
10826         }
10827         /* fall through */
10828       case BeginningOfGame:
10829       case PlayFromGameFile:
10830       case EndOfGame:
10831         EditGameEvent();
10832         if (gameMode != EditGame) return;
10833         break;
10834       case EditPosition:
10835         EditPositionDone();
10836         break;
10837       case AnalyzeMode:
10838       case AnalyzeFile:
10839         ExitAnalyzeMode();
10840         break;
10841       case EditGame:
10842       default:
10843         break;
10844     }
10845
10846     forwardMostMove = currentMove;
10847     ResurrectChessProgram();    /* in case first program isn't running */
10848
10849     if (second.pr == NULL) {
10850         StartChessProgram(&second);
10851         if (second.protocolVersion == 1) {
10852           TwoMachinesEventIfReady();
10853         } else {
10854           /* kludge: allow timeout for initial "feature" command */
10855           FreezeUI();
10856           DisplayMessage("", _("Starting second chess program"));
10857           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10858         }
10859         return;
10860     }
10861     DisplayMessage("", "");
10862     InitChessProgram(&second, FALSE);
10863     SendToProgram("force\n", &second);
10864     if (startedFromSetupPosition) {
10865         SendBoard(&second, backwardMostMove);
10866     if (appData.debugMode) {
10867         fprintf(debugFP, "Two Machines\n");
10868     }
10869     }
10870     for (i = backwardMostMove; i < forwardMostMove; i++) {
10871         SendMoveToProgram(i, &second);
10872     }
10873
10874     gameMode = TwoMachinesPlay;
10875     pausing = FALSE;
10876     ModeHighlight();
10877     SetGameInfo();
10878     DisplayTwoMachinesTitle();
10879     firstMove = TRUE;
10880     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10881         onmove = &first;
10882     } else {
10883         onmove = &second;
10884     }
10885
10886     SendToProgram(first.computerString, &first);
10887     if (first.sendName) {
10888       sprintf(buf, "name %s\n", second.tidy);
10889       SendToProgram(buf, &first);
10890     }
10891     SendToProgram(second.computerString, &second);
10892     if (second.sendName) {
10893       sprintf(buf, "name %s\n", first.tidy);
10894       SendToProgram(buf, &second);
10895     }
10896
10897     ResetClocks();
10898     if (!first.sendTime || !second.sendTime) {
10899         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10900         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10901     }
10902     if (onmove->sendTime) {
10903       if (onmove->useColors) {
10904         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10905       }
10906       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10907     }
10908     if (onmove->useColors) {
10909       SendToProgram(onmove->twoMachinesColor, onmove);
10910     }
10911     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10912 //    SendToProgram("go\n", onmove);
10913     onmove->maybeThinking = TRUE;
10914     SetMachineThinkingEnables();
10915
10916     StartClocks();
10917
10918     if(bookHit) { // [HGM] book: simulate book reply
10919         static char bookMove[MSG_SIZ]; // a bit generous?
10920
10921         programStats.nodes = programStats.depth = programStats.time = 
10922         programStats.score = programStats.got_only_move = 0;
10923         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10924
10925         strcpy(bookMove, "move ");
10926         strcat(bookMove, bookHit);
10927         HandleMachineMove(bookMove, &first);
10928     }
10929 }
10930
10931 void
10932 TrainingEvent()
10933 {
10934     if (gameMode == Training) {
10935       SetTrainingModeOff();
10936       gameMode = PlayFromGameFile;
10937       DisplayMessage("", _("Training mode off"));
10938     } else {
10939       gameMode = Training;
10940       animateTraining = appData.animate;
10941
10942       /* make sure we are not already at the end of the game */
10943       if (currentMove < forwardMostMove) {
10944         SetTrainingModeOn();
10945         DisplayMessage("", _("Training mode on"));
10946       } else {
10947         gameMode = PlayFromGameFile;
10948         DisplayError(_("Already at end of game"), 0);
10949       }
10950     }
10951     ModeHighlight();
10952 }
10953
10954 void
10955 IcsClientEvent()
10956 {
10957     if (!appData.icsActive) return;
10958     switch (gameMode) {
10959       case IcsPlayingWhite:
10960       case IcsPlayingBlack:
10961       case IcsObserving:
10962       case IcsIdle:
10963       case BeginningOfGame:
10964       case IcsExamining:
10965         return;
10966
10967       case EditGame:
10968         break;
10969
10970       case EditPosition:
10971         EditPositionDone();
10972         break;
10973
10974       case AnalyzeMode:
10975       case AnalyzeFile:
10976         ExitAnalyzeMode();
10977         break;
10978         
10979       default:
10980         EditGameEvent();
10981         break;
10982     }
10983
10984     gameMode = IcsIdle;
10985     ModeHighlight();
10986     return;
10987 }
10988
10989
10990 void
10991 EditGameEvent()
10992 {
10993     int i;
10994
10995     switch (gameMode) {
10996       case Training:
10997         SetTrainingModeOff();
10998         break;
10999       case MachinePlaysWhite:
11000       case MachinePlaysBlack:
11001       case BeginningOfGame:
11002         SendToProgram("force\n", &first);
11003         SetUserThinkingEnables();
11004         break;
11005       case PlayFromGameFile:
11006         (void) StopLoadGameTimer();
11007         if (gameFileFP != NULL) {
11008             gameFileFP = NULL;
11009         }
11010         break;
11011       case EditPosition:
11012         EditPositionDone();
11013         break;
11014       case AnalyzeMode:
11015       case AnalyzeFile:
11016         ExitAnalyzeMode();
11017         SendToProgram("force\n", &first);
11018         break;
11019       case TwoMachinesPlay:
11020         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11021         ResurrectChessProgram();
11022         SetUserThinkingEnables();
11023         break;
11024       case EndOfGame:
11025         ResurrectChessProgram();
11026         break;
11027       case IcsPlayingBlack:
11028       case IcsPlayingWhite:
11029         DisplayError(_("Warning: You are still playing a game"), 0);
11030         break;
11031       case IcsObserving:
11032         DisplayError(_("Warning: You are still observing a game"), 0);
11033         break;
11034       case IcsExamining:
11035         DisplayError(_("Warning: You are still examining a game"), 0);
11036         break;
11037       case IcsIdle:
11038         break;
11039       case EditGame:
11040       default:
11041         return;
11042     }
11043     
11044     pausing = FALSE;
11045     StopClocks();
11046     first.offeredDraw = second.offeredDraw = 0;
11047
11048     if (gameMode == PlayFromGameFile) {
11049         whiteTimeRemaining = timeRemaining[0][currentMove];
11050         blackTimeRemaining = timeRemaining[1][currentMove];
11051         DisplayTitle("");
11052     }
11053
11054     if (gameMode == MachinePlaysWhite ||
11055         gameMode == MachinePlaysBlack ||
11056         gameMode == TwoMachinesPlay ||
11057         gameMode == EndOfGame) {
11058         i = forwardMostMove;
11059         while (i > currentMove) {
11060             SendToProgram("undo\n", &first);
11061             i--;
11062         }
11063         whiteTimeRemaining = timeRemaining[0][currentMove];
11064         blackTimeRemaining = timeRemaining[1][currentMove];
11065         DisplayBothClocks();
11066         if (whiteFlag || blackFlag) {
11067             whiteFlag = blackFlag = 0;
11068         }
11069         DisplayTitle("");
11070     }           
11071     
11072     gameMode = EditGame;
11073     ModeHighlight();
11074     SetGameInfo();
11075 }
11076
11077
11078 void
11079 EditPositionEvent()
11080 {
11081     if (gameMode == EditPosition) {
11082         EditGameEvent();
11083         return;
11084     }
11085     
11086     EditGameEvent();
11087     if (gameMode != EditGame) return;
11088     
11089     gameMode = EditPosition;
11090     ModeHighlight();
11091     SetGameInfo();
11092     if (currentMove > 0)
11093       CopyBoard(boards[0], boards[currentMove]);
11094     
11095     blackPlaysFirst = !WhiteOnMove(currentMove);
11096     ResetClocks();
11097     currentMove = forwardMostMove = backwardMostMove = 0;
11098     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11099     DisplayMove(-1);
11100 }
11101
11102 void
11103 ExitAnalyzeMode()
11104 {
11105     /* [DM] icsEngineAnalyze - possible call from other functions */
11106     if (appData.icsEngineAnalyze) {
11107         appData.icsEngineAnalyze = FALSE;
11108
11109         DisplayMessage("",_("Close ICS engine analyze..."));
11110     }
11111     if (first.analysisSupport && first.analyzing) {
11112       SendToProgram("exit\n", &first);
11113       first.analyzing = FALSE;
11114     }
11115     thinkOutput[0] = NULLCHAR;
11116 }
11117
11118 void
11119 EditPositionDone()
11120 {
11121     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11122
11123     startedFromSetupPosition = TRUE;
11124     InitChessProgram(&first, FALSE);
11125     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11126     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11127         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11128         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11129     } else castlingRights[0][2] = -1;
11130     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11131         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11132         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11133     } else castlingRights[0][5] = -1;
11134     SendToProgram("force\n", &first);
11135     if (blackPlaysFirst) {
11136         strcpy(moveList[0], "");
11137         strcpy(parseList[0], "");
11138         currentMove = forwardMostMove = backwardMostMove = 1;
11139         CopyBoard(boards[1], boards[0]);
11140         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11141         { int i;
11142           epStatus[1] = epStatus[0];
11143           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11144         }
11145     } else {
11146         currentMove = forwardMostMove = backwardMostMove = 0;
11147     }
11148     SendBoard(&first, forwardMostMove);
11149     if (appData.debugMode) {
11150         fprintf(debugFP, "EditPosDone\n");
11151     }
11152     DisplayTitle("");
11153     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11154     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11155     gameMode = EditGame;
11156     ModeHighlight();
11157     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11158     ClearHighlights(); /* [AS] */
11159 }
11160
11161 /* Pause for `ms' milliseconds */
11162 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11163 void
11164 TimeDelay(ms)
11165      long ms;
11166 {
11167     TimeMark m1, m2;
11168
11169     GetTimeMark(&m1);
11170     do {
11171         GetTimeMark(&m2);
11172     } while (SubtractTimeMarks(&m2, &m1) < ms);
11173 }
11174
11175 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11176 void
11177 SendMultiLineToICS(buf)
11178      char *buf;
11179 {
11180     char temp[MSG_SIZ+1], *p;
11181     int len;
11182
11183     len = strlen(buf);
11184     if (len > MSG_SIZ)
11185       len = MSG_SIZ;
11186   
11187     strncpy(temp, buf, len);
11188     temp[len] = 0;
11189
11190     p = temp;
11191     while (*p) {
11192         if (*p == '\n' || *p == '\r')
11193           *p = ' ';
11194         ++p;
11195     }
11196
11197     strcat(temp, "\n");
11198     SendToICS(temp);
11199     SendToPlayer(temp, strlen(temp));
11200 }
11201
11202 void
11203 SetWhiteToPlayEvent()
11204 {
11205     if (gameMode == EditPosition) {
11206         blackPlaysFirst = FALSE;
11207         DisplayBothClocks();    /* works because currentMove is 0 */
11208     } else if (gameMode == IcsExamining) {
11209         SendToICS(ics_prefix);
11210         SendToICS("tomove white\n");
11211     }
11212 }
11213
11214 void
11215 SetBlackToPlayEvent()
11216 {
11217     if (gameMode == EditPosition) {
11218         blackPlaysFirst = TRUE;
11219         currentMove = 1;        /* kludge */
11220         DisplayBothClocks();
11221         currentMove = 0;
11222     } else if (gameMode == IcsExamining) {
11223         SendToICS(ics_prefix);
11224         SendToICS("tomove black\n");
11225     }
11226 }
11227
11228 void
11229 EditPositionMenuEvent(selection, x, y)
11230      ChessSquare selection;
11231      int x, y;
11232 {
11233     char buf[MSG_SIZ];
11234     ChessSquare piece = boards[0][y][x];
11235
11236     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11237
11238     switch (selection) {
11239       case ClearBoard:
11240         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11241             SendToICS(ics_prefix);
11242             SendToICS("bsetup clear\n");
11243         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11244             SendToICS(ics_prefix);
11245             SendToICS("clearboard\n");
11246         } else {
11247             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11248                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11249                 for (y = 0; y < BOARD_HEIGHT; y++) {
11250                     if (gameMode == IcsExamining) {
11251                         if (boards[currentMove][y][x] != EmptySquare) {
11252                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11253                                     AAA + x, ONE + y);
11254                             SendToICS(buf);
11255                         }
11256                     } else {
11257                         boards[0][y][x] = p;
11258                     }
11259                 }
11260             }
11261         }
11262         if (gameMode == EditPosition) {
11263             DrawPosition(FALSE, boards[0]);
11264         }
11265         break;
11266
11267       case WhitePlay:
11268         SetWhiteToPlayEvent();
11269         break;
11270
11271       case BlackPlay:
11272         SetBlackToPlayEvent();
11273         break;
11274
11275       case EmptySquare:
11276         if (gameMode == IcsExamining) {
11277             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11278             SendToICS(buf);
11279         } else {
11280             boards[0][y][x] = EmptySquare;
11281             DrawPosition(FALSE, boards[0]);
11282         }
11283         break;
11284
11285       case PromotePiece:
11286         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11287            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11288             selection = (ChessSquare) (PROMOTED piece);
11289         } else if(piece == EmptySquare) selection = WhiteSilver;
11290         else selection = (ChessSquare)((int)piece - 1);
11291         goto defaultlabel;
11292
11293       case DemotePiece:
11294         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11295            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11296             selection = (ChessSquare) (DEMOTED piece);
11297         } else if(piece == EmptySquare) selection = BlackSilver;
11298         else selection = (ChessSquare)((int)piece + 1);       
11299         goto defaultlabel;
11300
11301       case WhiteQueen:
11302       case BlackQueen:
11303         if(gameInfo.variant == VariantShatranj ||
11304            gameInfo.variant == VariantXiangqi  ||
11305            gameInfo.variant == VariantCourier    )
11306             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11307         goto defaultlabel;
11308
11309       case WhiteKing:
11310       case BlackKing:
11311         if(gameInfo.variant == VariantXiangqi)
11312             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11313         if(gameInfo.variant == VariantKnightmate)
11314             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11315       default:
11316         defaultlabel:
11317         if (gameMode == IcsExamining) {
11318             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11319                     PieceToChar(selection), AAA + x, ONE + y);
11320             SendToICS(buf);
11321         } else {
11322             boards[0][y][x] = selection;
11323             DrawPosition(FALSE, boards[0]);
11324         }
11325         break;
11326     }
11327 }
11328
11329
11330 void
11331 DropMenuEvent(selection, x, y)
11332      ChessSquare selection;
11333      int x, y;
11334 {
11335     ChessMove moveType;
11336
11337     switch (gameMode) {
11338       case IcsPlayingWhite:
11339       case MachinePlaysBlack:
11340         if (!WhiteOnMove(currentMove)) {
11341             DisplayMoveError(_("It is Black's turn"));
11342             return;
11343         }
11344         moveType = WhiteDrop;
11345         break;
11346       case IcsPlayingBlack:
11347       case MachinePlaysWhite:
11348         if (WhiteOnMove(currentMove)) {
11349             DisplayMoveError(_("It is White's turn"));
11350             return;
11351         }
11352         moveType = BlackDrop;
11353         break;
11354       case EditGame:
11355         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11356         break;
11357       default:
11358         return;
11359     }
11360
11361     if (moveType == BlackDrop && selection < BlackPawn) {
11362       selection = (ChessSquare) ((int) selection
11363                                  + (int) BlackPawn - (int) WhitePawn);
11364     }
11365     if (boards[currentMove][y][x] != EmptySquare) {
11366         DisplayMoveError(_("That square is occupied"));
11367         return;
11368     }
11369
11370     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11371 }
11372
11373 void
11374 AcceptEvent()
11375 {
11376     /* Accept a pending offer of any kind from opponent */
11377     
11378     if (appData.icsActive) {
11379         SendToICS(ics_prefix);
11380         SendToICS("accept\n");
11381     } else if (cmailMsgLoaded) {
11382         if (currentMove == cmailOldMove &&
11383             commentList[cmailOldMove] != NULL &&
11384             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11385                    "Black offers a draw" : "White offers a draw")) {
11386             TruncateGame();
11387             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11388             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11389         } else {
11390             DisplayError(_("There is no pending offer on this move"), 0);
11391             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11392         }
11393     } else {
11394         /* Not used for offers from chess program */
11395     }
11396 }
11397
11398 void
11399 DeclineEvent()
11400 {
11401     /* Decline a pending offer of any kind from opponent */
11402     
11403     if (appData.icsActive) {
11404         SendToICS(ics_prefix);
11405         SendToICS("decline\n");
11406     } else if (cmailMsgLoaded) {
11407         if (currentMove == cmailOldMove &&
11408             commentList[cmailOldMove] != NULL &&
11409             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11410                    "Black offers a draw" : "White offers a draw")) {
11411 #ifdef NOTDEF
11412             AppendComment(cmailOldMove, "Draw declined");
11413             DisplayComment(cmailOldMove - 1, "Draw declined");
11414 #endif /*NOTDEF*/
11415         } else {
11416             DisplayError(_("There is no pending offer on this move"), 0);
11417         }
11418     } else {
11419         /* Not used for offers from chess program */
11420     }
11421 }
11422
11423 void
11424 RematchEvent()
11425 {
11426     /* Issue ICS rematch command */
11427     if (appData.icsActive) {
11428         SendToICS(ics_prefix);
11429         SendToICS("rematch\n");
11430     }
11431 }
11432
11433 void
11434 CallFlagEvent()
11435 {
11436     /* Call your opponent's flag (claim a win on time) */
11437     if (appData.icsActive) {
11438         SendToICS(ics_prefix);
11439         SendToICS("flag\n");
11440     } else {
11441         switch (gameMode) {
11442           default:
11443             return;
11444           case MachinePlaysWhite:
11445             if (whiteFlag) {
11446                 if (blackFlag)
11447                   GameEnds(GameIsDrawn, "Both players ran out of time",
11448                            GE_PLAYER);
11449                 else
11450                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11451             } else {
11452                 DisplayError(_("Your opponent is not out of time"), 0);
11453             }
11454             break;
11455           case MachinePlaysBlack:
11456             if (blackFlag) {
11457                 if (whiteFlag)
11458                   GameEnds(GameIsDrawn, "Both players ran out of time",
11459                            GE_PLAYER);
11460                 else
11461                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11462             } else {
11463                 DisplayError(_("Your opponent is not out of time"), 0);
11464             }
11465             break;
11466         }
11467     }
11468 }
11469
11470 void
11471 DrawEvent()
11472 {
11473     /* Offer draw or accept pending draw offer from opponent */
11474     
11475     if (appData.icsActive) {
11476         /* Note: tournament rules require draw offers to be
11477            made after you make your move but before you punch
11478            your clock.  Currently ICS doesn't let you do that;
11479            instead, you immediately punch your clock after making
11480            a move, but you can offer a draw at any time. */
11481         
11482         SendToICS(ics_prefix);
11483         SendToICS("draw\n");
11484     } else if (cmailMsgLoaded) {
11485         if (currentMove == cmailOldMove &&
11486             commentList[cmailOldMove] != NULL &&
11487             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11488                    "Black offers a draw" : "White offers a draw")) {
11489             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11490             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11491         } else if (currentMove == cmailOldMove + 1) {
11492             char *offer = WhiteOnMove(cmailOldMove) ?
11493               "White offers a draw" : "Black offers a draw";
11494             AppendComment(currentMove, offer);
11495             DisplayComment(currentMove - 1, offer);
11496             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11497         } else {
11498             DisplayError(_("You must make your move before offering a draw"), 0);
11499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11500         }
11501     } else if (first.offeredDraw) {
11502         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11503     } else {
11504         if (first.sendDrawOffers) {
11505             SendToProgram("draw\n", &first);
11506             userOfferedDraw = TRUE;
11507         }
11508     }
11509 }
11510
11511 void
11512 AdjournEvent()
11513 {
11514     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11515     
11516     if (appData.icsActive) {
11517         SendToICS(ics_prefix);
11518         SendToICS("adjourn\n");
11519     } else {
11520         /* Currently GNU Chess doesn't offer or accept Adjourns */
11521     }
11522 }
11523
11524
11525 void
11526 AbortEvent()
11527 {
11528     /* Offer Abort or accept pending Abort offer from opponent */
11529     
11530     if (appData.icsActive) {
11531         SendToICS(ics_prefix);
11532         SendToICS("abort\n");
11533     } else {
11534         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11535     }
11536 }
11537
11538 void
11539 ResignEvent()
11540 {
11541     /* Resign.  You can do this even if it's not your turn. */
11542     
11543     if (appData.icsActive) {
11544         SendToICS(ics_prefix);
11545         SendToICS("resign\n");
11546     } else {
11547         switch (gameMode) {
11548           case MachinePlaysWhite:
11549             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11550             break;
11551           case MachinePlaysBlack:
11552             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11553             break;
11554           case EditGame:
11555             if (cmailMsgLoaded) {
11556                 TruncateGame();
11557                 if (WhiteOnMove(cmailOldMove)) {
11558                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11559                 } else {
11560                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11561                 }
11562                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11563             }
11564             break;
11565           default:
11566             break;
11567         }
11568     }
11569 }
11570
11571
11572 void
11573 StopObservingEvent()
11574 {
11575     /* Stop observing current games */
11576     SendToICS(ics_prefix);
11577     SendToICS("unobserve\n");
11578 }
11579
11580 void
11581 StopExaminingEvent()
11582 {
11583     /* Stop observing current game */
11584     SendToICS(ics_prefix);
11585     SendToICS("unexamine\n");
11586 }
11587
11588 void
11589 ForwardInner(target)
11590      int target;
11591 {
11592     int limit;
11593
11594     if (appData.debugMode)
11595         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11596                 target, currentMove, forwardMostMove);
11597
11598     if (gameMode == EditPosition)
11599       return;
11600
11601     if (gameMode == PlayFromGameFile && !pausing)
11602       PauseEvent();
11603     
11604     if (gameMode == IcsExamining && pausing)
11605       limit = pauseExamForwardMostMove;
11606     else
11607       limit = forwardMostMove;
11608     
11609     if (target > limit) target = limit;
11610
11611     if (target > 0 && moveList[target - 1][0]) {
11612         int fromX, fromY, toX, toY;
11613         toX = moveList[target - 1][2] - AAA;
11614         toY = moveList[target - 1][3] - ONE;
11615         if (moveList[target - 1][1] == '@') {
11616             if (appData.highlightLastMove) {
11617                 SetHighlights(-1, -1, toX, toY);
11618             }
11619         } else {
11620             fromX = moveList[target - 1][0] - AAA;
11621             fromY = moveList[target - 1][1] - ONE;
11622             if (target == currentMove + 1) {
11623                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11624             }
11625             if (appData.highlightLastMove) {
11626                 SetHighlights(fromX, fromY, toX, toY);
11627             }
11628         }
11629     }
11630     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11631         gameMode == Training || gameMode == PlayFromGameFile || 
11632         gameMode == AnalyzeFile) {
11633         while (currentMove < target) {
11634             SendMoveToProgram(currentMove++, &first);
11635         }
11636     } else {
11637         currentMove = target;
11638     }
11639     
11640     if (gameMode == EditGame || gameMode == EndOfGame) {
11641         whiteTimeRemaining = timeRemaining[0][currentMove];
11642         blackTimeRemaining = timeRemaining[1][currentMove];
11643     }
11644     DisplayBothClocks();
11645     DisplayMove(currentMove - 1);
11646     DrawPosition(FALSE, boards[currentMove]);
11647     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11648     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11649         DisplayComment(currentMove - 1, commentList[currentMove]);
11650     }
11651 }
11652
11653
11654 void
11655 ForwardEvent()
11656 {
11657     if (gameMode == IcsExamining && !pausing) {
11658         SendToICS(ics_prefix);
11659         SendToICS("forward\n");
11660     } else {
11661         ForwardInner(currentMove + 1);
11662     }
11663 }
11664
11665 void
11666 ToEndEvent()
11667 {
11668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11669         /* to optimze, we temporarily turn off analysis mode while we feed
11670          * the remaining moves to the engine. Otherwise we get analysis output
11671          * after each move.
11672          */ 
11673         if (first.analysisSupport) {
11674           SendToProgram("exit\nforce\n", &first);
11675           first.analyzing = FALSE;
11676         }
11677     }
11678         
11679     if (gameMode == IcsExamining && !pausing) {
11680         SendToICS(ics_prefix);
11681         SendToICS("forward 999999\n");
11682     } else {
11683         ForwardInner(forwardMostMove);
11684     }
11685
11686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11687         /* we have fed all the moves, so reactivate analysis mode */
11688         SendToProgram("analyze\n", &first);
11689         first.analyzing = TRUE;
11690         /*first.maybeThinking = TRUE;*/
11691         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11692     }
11693 }
11694
11695 void
11696 BackwardInner(target)
11697      int target;
11698 {
11699     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11700
11701     if (appData.debugMode)
11702         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11703                 target, currentMove, forwardMostMove);
11704
11705     if (gameMode == EditPosition) return;
11706     if (currentMove <= backwardMostMove) {
11707         ClearHighlights();
11708         DrawPosition(full_redraw, boards[currentMove]);
11709         return;
11710     }
11711     if (gameMode == PlayFromGameFile && !pausing)
11712       PauseEvent();
11713     
11714     if (moveList[target][0]) {
11715         int fromX, fromY, toX, toY;
11716         toX = moveList[target][2] - AAA;
11717         toY = moveList[target][3] - ONE;
11718         if (moveList[target][1] == '@') {
11719             if (appData.highlightLastMove) {
11720                 SetHighlights(-1, -1, toX, toY);
11721             }
11722         } else {
11723             fromX = moveList[target][0] - AAA;
11724             fromY = moveList[target][1] - ONE;
11725             if (target == currentMove - 1) {
11726                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11727             }
11728             if (appData.highlightLastMove) {
11729                 SetHighlights(fromX, fromY, toX, toY);
11730             }
11731         }
11732     }
11733     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11734         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11735         while (currentMove > target) {
11736             SendToProgram("undo\n", &first);
11737             currentMove--;
11738         }
11739     } else {
11740         currentMove = target;
11741     }
11742     
11743     if (gameMode == EditGame || gameMode == EndOfGame) {
11744         whiteTimeRemaining = timeRemaining[0][currentMove];
11745         blackTimeRemaining = timeRemaining[1][currentMove];
11746     }
11747     DisplayBothClocks();
11748     DisplayMove(currentMove - 1);
11749     DrawPosition(full_redraw, boards[currentMove]);
11750     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11751     // [HGM] PV info: routine tests if comment empty
11752     DisplayComment(currentMove - 1, commentList[currentMove]);
11753 }
11754
11755 void
11756 BackwardEvent()
11757 {
11758     if (gameMode == IcsExamining && !pausing) {
11759         SendToICS(ics_prefix);
11760         SendToICS("backward\n");
11761     } else {
11762         BackwardInner(currentMove - 1);
11763     }
11764 }
11765
11766 void
11767 ToStartEvent()
11768 {
11769     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11770         /* to optimze, we temporarily turn off analysis mode while we undo
11771          * all the moves. Otherwise we get analysis output after each undo.
11772          */ 
11773         if (first.analysisSupport) {
11774           SendToProgram("exit\nforce\n", &first);
11775           first.analyzing = FALSE;
11776         }
11777     }
11778
11779     if (gameMode == IcsExamining && !pausing) {
11780         SendToICS(ics_prefix);
11781         SendToICS("backward 999999\n");
11782     } else {
11783         BackwardInner(backwardMostMove);
11784     }
11785
11786     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11787         /* we have fed all the moves, so reactivate analysis mode */
11788         SendToProgram("analyze\n", &first);
11789         first.analyzing = TRUE;
11790         /*first.maybeThinking = TRUE;*/
11791         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11792     }
11793 }
11794
11795 void
11796 ToNrEvent(int to)
11797 {
11798   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11799   if (to >= forwardMostMove) to = forwardMostMove;
11800   if (to <= backwardMostMove) to = backwardMostMove;
11801   if (to < currentMove) {
11802     BackwardInner(to);
11803   } else {
11804     ForwardInner(to);
11805   }
11806 }
11807
11808 void
11809 RevertEvent()
11810 {
11811     if (gameMode != IcsExamining) {
11812         DisplayError(_("You are not examining a game"), 0);
11813         return;
11814     }
11815     if (pausing) {
11816         DisplayError(_("You can't revert while pausing"), 0);
11817         return;
11818     }
11819     SendToICS(ics_prefix);
11820     SendToICS("revert\n");
11821 }
11822
11823 void
11824 RetractMoveEvent()
11825 {
11826     switch (gameMode) {
11827       case MachinePlaysWhite:
11828       case MachinePlaysBlack:
11829         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11830             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11831             return;
11832         }
11833         if (forwardMostMove < 2) return;
11834         currentMove = forwardMostMove = forwardMostMove - 2;
11835         whiteTimeRemaining = timeRemaining[0][currentMove];
11836         blackTimeRemaining = timeRemaining[1][currentMove];
11837         DisplayBothClocks();
11838         DisplayMove(currentMove - 1);
11839         ClearHighlights();/*!! could figure this out*/
11840         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11841         SendToProgram("remove\n", &first);
11842         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11843         break;
11844
11845       case BeginningOfGame:
11846       default:
11847         break;
11848
11849       case IcsPlayingWhite:
11850       case IcsPlayingBlack:
11851         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11852             SendToICS(ics_prefix);
11853             SendToICS("takeback 2\n");
11854         } else {
11855             SendToICS(ics_prefix);
11856             SendToICS("takeback 1\n");
11857         }
11858         break;
11859     }
11860 }
11861
11862 void
11863 MoveNowEvent()
11864 {
11865     ChessProgramState *cps;
11866
11867     switch (gameMode) {
11868       case MachinePlaysWhite:
11869         if (!WhiteOnMove(forwardMostMove)) {
11870             DisplayError(_("It is your turn"), 0);
11871             return;
11872         }
11873         cps = &first;
11874         break;
11875       case MachinePlaysBlack:
11876         if (WhiteOnMove(forwardMostMove)) {
11877             DisplayError(_("It is your turn"), 0);
11878             return;
11879         }
11880         cps = &first;
11881         break;
11882       case TwoMachinesPlay:
11883         if (WhiteOnMove(forwardMostMove) ==
11884             (first.twoMachinesColor[0] == 'w')) {
11885             cps = &first;
11886         } else {
11887             cps = &second;
11888         }
11889         break;
11890       case BeginningOfGame:
11891       default:
11892         return;
11893     }
11894     SendToProgram("?\n", cps);
11895 }
11896
11897 void
11898 TruncateGameEvent()
11899 {
11900     EditGameEvent();
11901     if (gameMode != EditGame) return;
11902     TruncateGame();
11903 }
11904
11905 void
11906 TruncateGame()
11907 {
11908     if (forwardMostMove > currentMove) {
11909         if (gameInfo.resultDetails != NULL) {
11910             free(gameInfo.resultDetails);
11911             gameInfo.resultDetails = NULL;
11912             gameInfo.result = GameUnfinished;
11913         }
11914         forwardMostMove = currentMove;
11915         HistorySet(parseList, backwardMostMove, forwardMostMove,
11916                    currentMove-1);
11917     }
11918 }
11919
11920 void
11921 HintEvent()
11922 {
11923     if (appData.noChessProgram) return;
11924     switch (gameMode) {
11925       case MachinePlaysWhite:
11926         if (WhiteOnMove(forwardMostMove)) {
11927             DisplayError(_("Wait until your turn"), 0);
11928             return;
11929         }
11930         break;
11931       case BeginningOfGame:
11932       case MachinePlaysBlack:
11933         if (!WhiteOnMove(forwardMostMove)) {
11934             DisplayError(_("Wait until your turn"), 0);
11935             return;
11936         }
11937         break;
11938       default:
11939         DisplayError(_("No hint available"), 0);
11940         return;
11941     }
11942     SendToProgram("hint\n", &first);
11943     hintRequested = TRUE;
11944 }
11945
11946 void
11947 BookEvent()
11948 {
11949     if (appData.noChessProgram) return;
11950     switch (gameMode) {
11951       case MachinePlaysWhite:
11952         if (WhiteOnMove(forwardMostMove)) {
11953             DisplayError(_("Wait until your turn"), 0);
11954             return;
11955         }
11956         break;
11957       case BeginningOfGame:
11958       case MachinePlaysBlack:
11959         if (!WhiteOnMove(forwardMostMove)) {
11960             DisplayError(_("Wait until your turn"), 0);
11961             return;
11962         }
11963         break;
11964       case EditPosition:
11965         EditPositionDone();
11966         break;
11967       case TwoMachinesPlay:
11968         return;
11969       default:
11970         break;
11971     }
11972     SendToProgram("bk\n", &first);
11973     bookOutput[0] = NULLCHAR;
11974     bookRequested = TRUE;
11975 }
11976
11977 void
11978 AboutGameEvent()
11979 {
11980     char *tags = PGNTags(&gameInfo);
11981     TagsPopUp(tags, CmailMsg());
11982     free(tags);
11983 }
11984
11985 /* end button procedures */
11986
11987 void
11988 PrintPosition(fp, move)
11989      FILE *fp;
11990      int move;
11991 {
11992     int i, j;
11993     
11994     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11995         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11996             char c = PieceToChar(boards[move][i][j]);
11997             fputc(c == 'x' ? '.' : c, fp);
11998             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11999         }
12000     }
12001     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12002       fprintf(fp, "white to play\n");
12003     else
12004       fprintf(fp, "black to play\n");
12005 }
12006
12007 void
12008 PrintOpponents(fp)
12009      FILE *fp;
12010 {
12011     if (gameInfo.white != NULL) {
12012         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12013     } else {
12014         fprintf(fp, "\n");
12015     }
12016 }
12017
12018 /* Find last component of program's own name, using some heuristics */
12019 void
12020 TidyProgramName(prog, host, buf)
12021      char *prog, *host, buf[MSG_SIZ];
12022 {
12023     char *p, *q;
12024     int local = (strcmp(host, "localhost") == 0);
12025     while (!local && (p = strchr(prog, ';')) != NULL) {
12026         p++;
12027         while (*p == ' ') p++;
12028         prog = p;
12029     }
12030     if (*prog == '"' || *prog == '\'') {
12031         q = strchr(prog + 1, *prog);
12032     } else {
12033         q = strchr(prog, ' ');
12034     }
12035     if (q == NULL) q = prog + strlen(prog);
12036     p = q;
12037     while (p >= prog && *p != '/' && *p != '\\') p--;
12038     p++;
12039     if(p == prog && *p == '"') p++;
12040     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12041     memcpy(buf, p, q - p);
12042     buf[q - p] = NULLCHAR;
12043     if (!local) {
12044         strcat(buf, "@");
12045         strcat(buf, host);
12046     }
12047 }
12048
12049 char *
12050 TimeControlTagValue()
12051 {
12052     char buf[MSG_SIZ];
12053     if (!appData.clockMode) {
12054         strcpy(buf, "-");
12055     } else if (movesPerSession > 0) {
12056         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12057     } else if (timeIncrement == 0) {
12058         sprintf(buf, "%ld", timeControl/1000);
12059     } else {
12060         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12061     }
12062     return StrSave(buf);
12063 }
12064
12065 void
12066 SetGameInfo()
12067 {
12068     /* This routine is used only for certain modes */
12069     VariantClass v = gameInfo.variant;
12070     ClearGameInfo(&gameInfo);
12071     gameInfo.variant = v;
12072
12073     switch (gameMode) {
12074       case MachinePlaysWhite:
12075         gameInfo.event = StrSave( appData.pgnEventHeader );
12076         gameInfo.site = StrSave(HostName());
12077         gameInfo.date = PGNDate();
12078         gameInfo.round = StrSave("-");
12079         gameInfo.white = StrSave(first.tidy);
12080         gameInfo.black = StrSave(UserName());
12081         gameInfo.timeControl = TimeControlTagValue();
12082         break;
12083
12084       case MachinePlaysBlack:
12085         gameInfo.event = StrSave( appData.pgnEventHeader );
12086         gameInfo.site = StrSave(HostName());
12087         gameInfo.date = PGNDate();
12088         gameInfo.round = StrSave("-");
12089         gameInfo.white = StrSave(UserName());
12090         gameInfo.black = StrSave(first.tidy);
12091         gameInfo.timeControl = TimeControlTagValue();
12092         break;
12093
12094       case TwoMachinesPlay:
12095         gameInfo.event = StrSave( appData.pgnEventHeader );
12096         gameInfo.site = StrSave(HostName());
12097         gameInfo.date = PGNDate();
12098         if (matchGame > 0) {
12099             char buf[MSG_SIZ];
12100             sprintf(buf, "%d", matchGame);
12101             gameInfo.round = StrSave(buf);
12102         } else {
12103             gameInfo.round = StrSave("-");
12104         }
12105         if (first.twoMachinesColor[0] == 'w') {
12106             gameInfo.white = StrSave(first.tidy);
12107             gameInfo.black = StrSave(second.tidy);
12108         } else {
12109             gameInfo.white = StrSave(second.tidy);
12110             gameInfo.black = StrSave(first.tidy);
12111         }
12112         gameInfo.timeControl = TimeControlTagValue();
12113         break;
12114
12115       case EditGame:
12116         gameInfo.event = StrSave("Edited game");
12117         gameInfo.site = StrSave(HostName());
12118         gameInfo.date = PGNDate();
12119         gameInfo.round = StrSave("-");
12120         gameInfo.white = StrSave("-");
12121         gameInfo.black = StrSave("-");
12122         break;
12123
12124       case EditPosition:
12125         gameInfo.event = StrSave("Edited position");
12126         gameInfo.site = StrSave(HostName());
12127         gameInfo.date = PGNDate();
12128         gameInfo.round = StrSave("-");
12129         gameInfo.white = StrSave("-");
12130         gameInfo.black = StrSave("-");
12131         break;
12132
12133       case IcsPlayingWhite:
12134       case IcsPlayingBlack:
12135       case IcsObserving:
12136       case IcsExamining:
12137         break;
12138
12139       case PlayFromGameFile:
12140         gameInfo.event = StrSave("Game from non-PGN file");
12141         gameInfo.site = StrSave(HostName());
12142         gameInfo.date = PGNDate();
12143         gameInfo.round = StrSave("-");
12144         gameInfo.white = StrSave("?");
12145         gameInfo.black = StrSave("?");
12146         break;
12147
12148       default:
12149         break;
12150     }
12151 }
12152
12153 void
12154 ReplaceComment(index, text)
12155      int index;
12156      char *text;
12157 {
12158     int len;
12159
12160     while (*text == '\n') text++;
12161     len = strlen(text);
12162     while (len > 0 && text[len - 1] == '\n') len--;
12163
12164     if (commentList[index] != NULL)
12165       free(commentList[index]);
12166
12167     if (len == 0) {
12168         commentList[index] = NULL;
12169         return;
12170     }
12171     commentList[index] = (char *) malloc(len + 2);
12172     strncpy(commentList[index], text, len);
12173     commentList[index][len] = '\n';
12174     commentList[index][len + 1] = NULLCHAR;
12175 }
12176
12177 void
12178 CrushCRs(text)
12179      char *text;
12180 {
12181   char *p = text;
12182   char *q = text;
12183   char ch;
12184
12185   do {
12186     ch = *p++;
12187     if (ch == '\r') continue;
12188     *q++ = ch;
12189   } while (ch != '\0');
12190 }
12191
12192 void
12193 AppendComment(index, text)
12194      int index;
12195      char *text;
12196 {
12197     int oldlen, len;
12198     char *old;
12199
12200     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12201
12202     CrushCRs(text);
12203     while (*text == '\n') text++;
12204     len = strlen(text);
12205     while (len > 0 && text[len - 1] == '\n') len--;
12206
12207     if (len == 0) return;
12208
12209     if (commentList[index] != NULL) {
12210         old = commentList[index];
12211         oldlen = strlen(old);
12212         commentList[index] = (char *) malloc(oldlen + len + 2);
12213         strcpy(commentList[index], old);
12214         free(old);
12215         strncpy(&commentList[index][oldlen], text, len);
12216         commentList[index][oldlen + len] = '\n';
12217         commentList[index][oldlen + len + 1] = NULLCHAR;
12218     } else {
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
12226 static char * FindStr( char * text, char * sub_text )
12227 {
12228     char * result = strstr( text, sub_text );
12229
12230     if( result != NULL ) {
12231         result += strlen( sub_text );
12232     }
12233
12234     return result;
12235 }
12236
12237 /* [AS] Try to extract PV info from PGN comment */
12238 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12239 char *GetInfoFromComment( int index, char * text )
12240 {
12241     char * sep = text;
12242
12243     if( text != NULL && index > 0 ) {
12244         int score = 0;
12245         int depth = 0;
12246         int time = -1, sec = 0, deci;
12247         char * s_eval = FindStr( text, "[%eval " );
12248         char * s_emt = FindStr( text, "[%emt " );
12249
12250         if( s_eval != NULL || s_emt != NULL ) {
12251             /* New style */
12252             char delim;
12253
12254             if( s_eval != NULL ) {
12255                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12256                     return text;
12257                 }
12258
12259                 if( delim != ']' ) {
12260                     return text;
12261                 }
12262             }
12263
12264             if( s_emt != NULL ) {
12265             }
12266         }
12267         else {
12268             /* We expect something like: [+|-]nnn.nn/dd */
12269             int score_lo = 0;
12270
12271             sep = strchr( text, '/' );
12272             if( sep == NULL || sep < (text+4) ) {
12273                 return text;
12274             }
12275
12276             time = -1; sec = -1; deci = -1;
12277             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12278                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12279                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12280                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12281                 return text;
12282             }
12283
12284             if( score_lo < 0 || score_lo >= 100 ) {
12285                 return text;
12286             }
12287
12288             if(sec >= 0) time = 600*time + 10*sec; else
12289             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12290
12291             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12292
12293             /* [HGM] PV time: now locate end of PV info */
12294             while( *++sep >= '0' && *sep <= '9'); // strip depth
12295             if(time >= 0)
12296             while( *++sep >= '0' && *sep <= '9'); // strip time
12297             if(sec >= 0)
12298             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12299             if(deci >= 0)
12300             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12301             while(*sep == ' ') sep++;
12302         }
12303
12304         if( depth <= 0 ) {
12305             return text;
12306         }
12307
12308         if( time < 0 ) {
12309             time = -1;
12310         }
12311
12312         pvInfoList[index-1].depth = depth;
12313         pvInfoList[index-1].score = score;
12314         pvInfoList[index-1].time  = 10*time; // centi-sec
12315     }
12316     return sep;
12317 }
12318
12319 void
12320 SendToProgram(message, cps)
12321      char *message;
12322      ChessProgramState *cps;
12323 {
12324     int count, outCount, error;
12325     char buf[MSG_SIZ];
12326
12327     if (cps->pr == NULL) return;
12328     Attention(cps);
12329     
12330     if (appData.debugMode) {
12331         TimeMark now;
12332         GetTimeMark(&now);
12333         fprintf(debugFP, "%ld >%-6s: %s", 
12334                 SubtractTimeMarks(&now, &programStartTime),
12335                 cps->which, message);
12336     }
12337     
12338     count = strlen(message);
12339     outCount = OutputToProcess(cps->pr, message, count, &error);
12340     if (outCount < count && !exiting 
12341                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12342         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12343         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12344             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12345                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12346                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12347             } else {
12348                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12349             }
12350             gameInfo.resultDetails = buf;
12351         }
12352         DisplayFatalError(buf, error, 1);
12353     }
12354 }
12355
12356 void
12357 ReceiveFromProgram(isr, closure, message, count, error)
12358      InputSourceRef isr;
12359      VOIDSTAR closure;
12360      char *message;
12361      int count;
12362      int error;
12363 {
12364     char *end_str;
12365     char buf[MSG_SIZ];
12366     ChessProgramState *cps = (ChessProgramState *)closure;
12367
12368     if (isr != cps->isr) return; /* Killed intentionally */
12369     if (count <= 0) {
12370         if (count == 0) {
12371             sprintf(buf,
12372                     _("Error: %s chess program (%s) exited unexpectedly"),
12373                     cps->which, cps->program);
12374         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12375                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12376                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12377                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12378                 } else {
12379                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12380                 }
12381                 gameInfo.resultDetails = buf;
12382             }
12383             RemoveInputSource(cps->isr);
12384             DisplayFatalError(buf, 0, 1);
12385         } else {
12386             sprintf(buf,
12387                     _("Error reading from %s chess program (%s)"),
12388                     cps->which, cps->program);
12389             RemoveInputSource(cps->isr);
12390
12391             /* [AS] Program is misbehaving badly... kill it */
12392             if( count == -2 ) {
12393                 DestroyChildProcess( cps->pr, 9 );
12394                 cps->pr = NoProc;
12395             }
12396
12397             DisplayFatalError(buf, error, 1);
12398         }
12399         return;
12400     }
12401     
12402     if ((end_str = strchr(message, '\r')) != NULL)
12403       *end_str = NULLCHAR;
12404     if ((end_str = strchr(message, '\n')) != NULL)
12405       *end_str = NULLCHAR;
12406     
12407     if (appData.debugMode) {
12408         TimeMark now; int print = 1;
12409         char *quote = ""; char c; int i;
12410
12411         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12412                 char start = message[0];
12413                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12414                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12415                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12416                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12417                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12418                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12419                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12420                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12421                         { quote = "# "; print = (appData.engineComments == 2); }
12422                 message[0] = start; // restore original message
12423         }
12424         if(print) {
12425                 GetTimeMark(&now);
12426                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12427                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12428                         quote,
12429                         message);
12430         }
12431     }
12432
12433     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12434     if (appData.icsEngineAnalyze) {
12435         if (strstr(message, "whisper") != NULL ||
12436              strstr(message, "kibitz") != NULL || 
12437             strstr(message, "tellics") != NULL) return;
12438     }
12439
12440     HandleMachineMove(message, cps);
12441 }
12442
12443
12444 void
12445 SendTimeControl(cps, mps, tc, inc, sd, st)
12446      ChessProgramState *cps;
12447      int mps, inc, sd, st;
12448      long tc;
12449 {
12450     char buf[MSG_SIZ];
12451     int seconds;
12452
12453     if( timeControl_2 > 0 ) {
12454         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12455             tc = timeControl_2;
12456         }
12457     }
12458     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12459     inc /= cps->timeOdds;
12460     st  /= cps->timeOdds;
12461
12462     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12463
12464     if (st > 0) {
12465       /* Set exact time per move, normally using st command */
12466       if (cps->stKludge) {
12467         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12468         seconds = st % 60;
12469         if (seconds == 0) {
12470           sprintf(buf, "level 1 %d\n", st/60);
12471         } else {
12472           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12473         }
12474       } else {
12475         sprintf(buf, "st %d\n", st);
12476       }
12477     } else {
12478       /* Set conventional or incremental time control, using level command */
12479       if (seconds == 0) {
12480         /* Note old gnuchess bug -- minutes:seconds used to not work.
12481            Fixed in later versions, but still avoid :seconds
12482            when seconds is 0. */
12483         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12484       } else {
12485         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12486                 seconds, inc/1000);
12487       }
12488     }
12489     SendToProgram(buf, cps);
12490
12491     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12492     /* Orthogonally, limit search to given depth */
12493     if (sd > 0) {
12494       if (cps->sdKludge) {
12495         sprintf(buf, "depth\n%d\n", sd);
12496       } else {
12497         sprintf(buf, "sd %d\n", sd);
12498       }
12499       SendToProgram(buf, cps);
12500     }
12501
12502     if(cps->nps > 0) { /* [HGM] nps */
12503         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12504         else {
12505                 sprintf(buf, "nps %d\n", cps->nps);
12506               SendToProgram(buf, cps);
12507         }
12508     }
12509 }
12510
12511 ChessProgramState *WhitePlayer()
12512 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12513 {
12514     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12515        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12516         return &second;
12517     return &first;
12518 }
12519
12520 void
12521 SendTimeRemaining(cps, machineWhite)
12522      ChessProgramState *cps;
12523      int /*boolean*/ machineWhite;
12524 {
12525     char message[MSG_SIZ];
12526     long time, otime;
12527
12528     /* Note: this routine must be called when the clocks are stopped
12529        or when they have *just* been set or switched; otherwise
12530        it will be off by the time since the current tick started.
12531     */
12532     if (machineWhite) {
12533         time = whiteTimeRemaining / 10;
12534         otime = blackTimeRemaining / 10;
12535     } else {
12536         time = blackTimeRemaining / 10;
12537         otime = whiteTimeRemaining / 10;
12538     }
12539     /* [HGM] translate opponent's time by time-odds factor */
12540     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12541     if (appData.debugMode) {
12542         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12543     }
12544
12545     if (time <= 0) time = 1;
12546     if (otime <= 0) otime = 1;
12547     
12548     sprintf(message, "time %ld\n", time);
12549     SendToProgram(message, cps);
12550
12551     sprintf(message, "otim %ld\n", otime);
12552     SendToProgram(message, cps);
12553 }
12554
12555 int
12556 BoolFeature(p, name, loc, cps)
12557      char **p;
12558      char *name;
12559      int *loc;
12560      ChessProgramState *cps;
12561 {
12562   char buf[MSG_SIZ];
12563   int len = strlen(name);
12564   int val;
12565   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12566     (*p) += len + 1;
12567     sscanf(*p, "%d", &val);
12568     *loc = (val != 0);
12569     while (**p && **p != ' ') (*p)++;
12570     sprintf(buf, "accepted %s\n", name);
12571     SendToProgram(buf, cps);
12572     return TRUE;
12573   }
12574   return FALSE;
12575 }
12576
12577 int
12578 IntFeature(p, name, loc, cps)
12579      char **p;
12580      char *name;
12581      int *loc;
12582      ChessProgramState *cps;
12583 {
12584   char buf[MSG_SIZ];
12585   int len = strlen(name);
12586   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12587     (*p) += len + 1;
12588     sscanf(*p, "%d", loc);
12589     while (**p && **p != ' ') (*p)++;
12590     sprintf(buf, "accepted %s\n", name);
12591     SendToProgram(buf, cps);
12592     return TRUE;
12593   }
12594   return FALSE;
12595 }
12596
12597 int
12598 StringFeature(p, name, loc, cps)
12599      char **p;
12600      char *name;
12601      char loc[];
12602      ChessProgramState *cps;
12603 {
12604   char buf[MSG_SIZ];
12605   int len = strlen(name);
12606   if (strncmp((*p), name, len) == 0
12607       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12608     (*p) += len + 2;
12609     sscanf(*p, "%[^\"]", loc);
12610     while (**p && **p != '\"') (*p)++;
12611     if (**p == '\"') (*p)++;
12612     sprintf(buf, "accepted %s\n", name);
12613     SendToProgram(buf, cps);
12614     return TRUE;
12615   }
12616   return FALSE;
12617 }
12618
12619 int 
12620 ParseOption(Option *opt, ChessProgramState *cps)
12621 // [HGM] options: process the string that defines an engine option, and determine
12622 // name, type, default value, and allowed value range
12623 {
12624         char *p, *q, buf[MSG_SIZ];
12625         int n, min = (-1)<<31, max = 1<<31, def;
12626
12627         if(p = strstr(opt->name, " -spin ")) {
12628             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12629             if(max < min) max = min; // enforce consistency
12630             if(def < min) def = min;
12631             if(def > max) def = max;
12632             opt->value = def;
12633             opt->min = min;
12634             opt->max = max;
12635             opt->type = Spin;
12636         } else if((p = strstr(opt->name, " -slider "))) {
12637             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12638             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12639             if(max < min) max = min; // enforce consistency
12640             if(def < min) def = min;
12641             if(def > max) def = max;
12642             opt->value = def;
12643             opt->min = min;
12644             opt->max = max;
12645             opt->type = Spin; // Slider;
12646         } else if((p = strstr(opt->name, " -string "))) {
12647             opt->textValue = p+9;
12648             opt->type = TextBox;
12649         } else if((p = strstr(opt->name, " -file "))) {
12650             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12651             opt->textValue = p+7;
12652             opt->type = TextBox; // FileName;
12653         } else if((p = strstr(opt->name, " -path "))) {
12654             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12655             opt->textValue = p+7;
12656             opt->type = TextBox; // PathName;
12657         } else if(p = strstr(opt->name, " -check ")) {
12658             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12659             opt->value = (def != 0);
12660             opt->type = CheckBox;
12661         } else if(p = strstr(opt->name, " -combo ")) {
12662             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12663             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12664             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12665             opt->value = n = 0;
12666             while(q = StrStr(q, " /// ")) {
12667                 n++; *q = 0;    // count choices, and null-terminate each of them
12668                 q += 5;
12669                 if(*q == '*') { // remember default, which is marked with * prefix
12670                     q++;
12671                     opt->value = n;
12672                 }
12673                 cps->comboList[cps->comboCnt++] = q;
12674             }
12675             cps->comboList[cps->comboCnt++] = NULL;
12676             opt->max = n + 1;
12677             opt->type = ComboBox;
12678         } else if(p = strstr(opt->name, " -button")) {
12679             opt->type = Button;
12680         } else if(p = strstr(opt->name, " -save")) {
12681             opt->type = SaveButton;
12682         } else return FALSE;
12683         *p = 0; // terminate option name
12684         // now look if the command-line options define a setting for this engine option.
12685         if(cps->optionSettings && cps->optionSettings[0])
12686             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12687         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12688                 sprintf(buf, "option %s", p);
12689                 if(p = strstr(buf, ",")) *p = 0;
12690                 strcat(buf, "\n");
12691                 SendToProgram(buf, cps);
12692         }
12693         return TRUE;
12694 }
12695
12696 void
12697 FeatureDone(cps, val)
12698      ChessProgramState* cps;
12699      int val;
12700 {
12701   DelayedEventCallback cb = GetDelayedEvent();
12702   if ((cb == InitBackEnd3 && cps == &first) ||
12703       (cb == TwoMachinesEventIfReady && cps == &second)) {
12704     CancelDelayedEvent();
12705     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12706   }
12707   cps->initDone = val;
12708 }
12709
12710 /* Parse feature command from engine */
12711 void
12712 ParseFeatures(args, cps)
12713      char* args;
12714      ChessProgramState *cps;  
12715 {
12716   char *p = args;
12717   char *q;
12718   int val;
12719   char buf[MSG_SIZ];
12720
12721   for (;;) {
12722     while (*p == ' ') p++;
12723     if (*p == NULLCHAR) return;
12724
12725     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12726     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12727     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12728     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12729     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12730     if (BoolFeature(&p, "reuse", &val, cps)) {
12731       /* Engine can disable reuse, but can't enable it if user said no */
12732       if (!val) cps->reuse = FALSE;
12733       continue;
12734     }
12735     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12736     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12737       if (gameMode == TwoMachinesPlay) {
12738         DisplayTwoMachinesTitle();
12739       } else {
12740         DisplayTitle("");
12741       }
12742       continue;
12743     }
12744     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12745     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12746     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12747     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12748     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12749     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12750     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12751     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12752     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12753     if (IntFeature(&p, "done", &val, cps)) {
12754       FeatureDone(cps, val);
12755       continue;
12756     }
12757     /* Added by Tord: */
12758     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12759     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12760     /* End of additions by Tord */
12761
12762     /* [HGM] added features: */
12763     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12764     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12765     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12766     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12767     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12768     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12769     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12770         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12771             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12772             SendToProgram(buf, cps);
12773             continue;
12774         }
12775         if(cps->nrOptions >= MAX_OPTIONS) {
12776             cps->nrOptions--;
12777             sprintf(buf, "%s engine has too many options\n", cps->which);
12778             DisplayError(buf, 0);
12779         }
12780         continue;
12781     }
12782     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12783     /* End of additions by HGM */
12784
12785     /* unknown feature: complain and skip */
12786     q = p;
12787     while (*q && *q != '=') q++;
12788     sprintf(buf, "rejected %.*s\n", q-p, p);
12789     SendToProgram(buf, cps);
12790     p = q;
12791     if (*p == '=') {
12792       p++;
12793       if (*p == '\"') {
12794         p++;
12795         while (*p && *p != '\"') p++;
12796         if (*p == '\"') p++;
12797       } else {
12798         while (*p && *p != ' ') p++;
12799       }
12800     }
12801   }
12802
12803 }
12804
12805 void
12806 PeriodicUpdatesEvent(newState)
12807      int newState;
12808 {
12809     if (newState == appData.periodicUpdates)
12810       return;
12811
12812     appData.periodicUpdates=newState;
12813
12814     /* Display type changes, so update it now */
12815 //    DisplayAnalysis();
12816
12817     /* Get the ball rolling again... */
12818     if (newState) {
12819         AnalysisPeriodicEvent(1);
12820         StartAnalysisClock();
12821     }
12822 }
12823
12824 void
12825 PonderNextMoveEvent(newState)
12826      int newState;
12827 {
12828     if (newState == appData.ponderNextMove) return;
12829     if (gameMode == EditPosition) EditPositionDone();
12830     if (newState) {
12831         SendToProgram("hard\n", &first);
12832         if (gameMode == TwoMachinesPlay) {
12833             SendToProgram("hard\n", &second);
12834         }
12835     } else {
12836         SendToProgram("easy\n", &first);
12837         thinkOutput[0] = NULLCHAR;
12838         if (gameMode == TwoMachinesPlay) {
12839             SendToProgram("easy\n", &second);
12840         }
12841     }
12842     appData.ponderNextMove = newState;
12843 }
12844
12845 void
12846 NewSettingEvent(option, command, value)
12847      char *command;
12848      int option, value;
12849 {
12850     char buf[MSG_SIZ];
12851
12852     if (gameMode == EditPosition) EditPositionDone();
12853     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12854     SendToProgram(buf, &first);
12855     if (gameMode == TwoMachinesPlay) {
12856         SendToProgram(buf, &second);
12857     }
12858 }
12859
12860 void
12861 ShowThinkingEvent()
12862 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12863 {
12864     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12865     int newState = appData.showThinking
12866         // [HGM] thinking: other features now need thinking output as well
12867         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12868     
12869     if (oldState == newState) return;
12870     oldState = newState;
12871     if (gameMode == EditPosition) EditPositionDone();
12872     if (oldState) {
12873         SendToProgram("post\n", &first);
12874         if (gameMode == TwoMachinesPlay) {
12875             SendToProgram("post\n", &second);
12876         }
12877     } else {
12878         SendToProgram("nopost\n", &first);
12879         thinkOutput[0] = NULLCHAR;
12880         if (gameMode == TwoMachinesPlay) {
12881             SendToProgram("nopost\n", &second);
12882         }
12883     }
12884 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12885 }
12886
12887 void
12888 AskQuestionEvent(title, question, replyPrefix, which)
12889      char *title; char *question; char *replyPrefix; char *which;
12890 {
12891   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12892   if (pr == NoProc) return;
12893   AskQuestion(title, question, replyPrefix, pr);
12894 }
12895
12896 void
12897 DisplayMove(moveNumber)
12898      int moveNumber;
12899 {
12900     char message[MSG_SIZ];
12901     char res[MSG_SIZ];
12902     char cpThinkOutput[MSG_SIZ];
12903
12904     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12905     
12906     if (moveNumber == forwardMostMove - 1 || 
12907         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12908
12909         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12910
12911         if (strchr(cpThinkOutput, '\n')) {
12912             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12913         }
12914     } else {
12915         *cpThinkOutput = NULLCHAR;
12916     }
12917
12918     /* [AS] Hide thinking from human user */
12919     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12920         *cpThinkOutput = NULLCHAR;
12921         if( thinkOutput[0] != NULLCHAR ) {
12922             int i;
12923
12924             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12925                 cpThinkOutput[i] = '.';
12926             }
12927             cpThinkOutput[i] = NULLCHAR;
12928             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12929         }
12930     }
12931
12932     if (moveNumber == forwardMostMove - 1 &&
12933         gameInfo.resultDetails != NULL) {
12934         if (gameInfo.resultDetails[0] == NULLCHAR) {
12935             sprintf(res, " %s", PGNResult(gameInfo.result));
12936         } else {
12937             sprintf(res, " {%s} %s",
12938                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12939         }
12940     } else {
12941         res[0] = NULLCHAR;
12942     }
12943
12944     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12945         DisplayMessage(res, cpThinkOutput);
12946     } else {
12947         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12948                 WhiteOnMove(moveNumber) ? " " : ".. ",
12949                 parseList[moveNumber], res);
12950         DisplayMessage(message, cpThinkOutput);
12951     }
12952 }
12953
12954 void
12955 DisplayComment(moveNumber, text)
12956      int moveNumber;
12957      char *text;
12958 {
12959     char title[MSG_SIZ];
12960     char buf[8000]; // comment can be long!
12961     int score, depth;
12962
12963     if( appData.autoDisplayComment ) {
12964         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12965             strcpy(title, "Comment");
12966         } else {
12967             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12968                     WhiteOnMove(moveNumber) ? " " : ".. ",
12969                     parseList[moveNumber]);
12970         }
12971         // [HGM] PV info: display PV info together with (or as) comment
12972         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12973             if(text == NULL) text = "";                                           
12974             score = pvInfoList[moveNumber].score;
12975             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12976                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12977             text = buf;
12978         }
12979     } else title[0] = 0;
12980
12981     if (text != NULL)
12982         CommentPopUp(title, text);
12983 }
12984
12985 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12986  * might be busy thinking or pondering.  It can be omitted if your
12987  * gnuchess is configured to stop thinking immediately on any user
12988  * input.  However, that gnuchess feature depends on the FIONREAD
12989  * ioctl, which does not work properly on some flavors of Unix.
12990  */
12991 void
12992 Attention(cps)
12993      ChessProgramState *cps;
12994 {
12995 #if ATTENTION
12996     if (!cps->useSigint) return;
12997     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12998     switch (gameMode) {
12999       case MachinePlaysWhite:
13000       case MachinePlaysBlack:
13001       case TwoMachinesPlay:
13002       case IcsPlayingWhite:
13003       case IcsPlayingBlack:
13004       case AnalyzeMode:
13005       case AnalyzeFile:
13006         /* Skip if we know it isn't thinking */
13007         if (!cps->maybeThinking) return;
13008         if (appData.debugMode)
13009           fprintf(debugFP, "Interrupting %s\n", cps->which);
13010         InterruptChildProcess(cps->pr);
13011         cps->maybeThinking = FALSE;
13012         break;
13013       default:
13014         break;
13015     }
13016 #endif /*ATTENTION*/
13017 }
13018
13019 int
13020 CheckFlags()
13021 {
13022     if (whiteTimeRemaining <= 0) {
13023         if (!whiteFlag) {
13024             whiteFlag = TRUE;
13025             if (appData.icsActive) {
13026                 if (appData.autoCallFlag &&
13027                     gameMode == IcsPlayingBlack && !blackFlag) {
13028                   SendToICS(ics_prefix);
13029                   SendToICS("flag\n");
13030                 }
13031             } else {
13032                 if (blackFlag) {
13033                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13034                 } else {
13035                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13036                     if (appData.autoCallFlag) {
13037                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13038                         return TRUE;
13039                     }
13040                 }
13041             }
13042         }
13043     }
13044     if (blackTimeRemaining <= 0) {
13045         if (!blackFlag) {
13046             blackFlag = TRUE;
13047             if (appData.icsActive) {
13048                 if (appData.autoCallFlag &&
13049                     gameMode == IcsPlayingWhite && !whiteFlag) {
13050                   SendToICS(ics_prefix);
13051                   SendToICS("flag\n");
13052                 }
13053             } else {
13054                 if (whiteFlag) {
13055                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13056                 } else {
13057                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13058                     if (appData.autoCallFlag) {
13059                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13060                         return TRUE;
13061                     }
13062                 }
13063             }
13064         }
13065     }
13066     return FALSE;
13067 }
13068
13069 void
13070 CheckTimeControl()
13071 {
13072     if (!appData.clockMode || appData.icsActive ||
13073         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13074
13075     /*
13076      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13077      */
13078     if ( !WhiteOnMove(forwardMostMove) )
13079         /* White made time control */
13080         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13081         /* [HGM] time odds: correct new time quota for time odds! */
13082                                             / WhitePlayer()->timeOdds;
13083       else
13084         /* Black made time control */
13085         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13086                                             / WhitePlayer()->other->timeOdds;
13087 }
13088
13089 void
13090 DisplayBothClocks()
13091 {
13092     int wom = gameMode == EditPosition ?
13093       !blackPlaysFirst : WhiteOnMove(currentMove);
13094     DisplayWhiteClock(whiteTimeRemaining, wom);
13095     DisplayBlackClock(blackTimeRemaining, !wom);
13096 }
13097
13098
13099 /* Timekeeping seems to be a portability nightmare.  I think everyone
13100    has ftime(), but I'm really not sure, so I'm including some ifdefs
13101    to use other calls if you don't.  Clocks will be less accurate if
13102    you have neither ftime nor gettimeofday.
13103 */
13104
13105 /* VS 2008 requires the #include outside of the function */
13106 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13107 #include <sys/timeb.h>
13108 #endif
13109
13110 /* Get the current time as a TimeMark */
13111 void
13112 GetTimeMark(tm)
13113      TimeMark *tm;
13114 {
13115 #if HAVE_GETTIMEOFDAY
13116
13117     struct timeval timeVal;
13118     struct timezone timeZone;
13119
13120     gettimeofday(&timeVal, &timeZone);
13121     tm->sec = (long) timeVal.tv_sec; 
13122     tm->ms = (int) (timeVal.tv_usec / 1000L);
13123
13124 #else /*!HAVE_GETTIMEOFDAY*/
13125 #if HAVE_FTIME
13126
13127 // include <sys/timeb.h> / moved to just above start of function
13128     struct timeb timeB;
13129
13130     ftime(&timeB);
13131     tm->sec = (long) timeB.time;
13132     tm->ms = (int) timeB.millitm;
13133
13134 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13135     tm->sec = (long) time(NULL);
13136     tm->ms = 0;
13137 #endif
13138 #endif
13139 }
13140
13141 /* Return the difference in milliseconds between two
13142    time marks.  We assume the difference will fit in a long!
13143 */
13144 long
13145 SubtractTimeMarks(tm2, tm1)
13146      TimeMark *tm2, *tm1;
13147 {
13148     return 1000L*(tm2->sec - tm1->sec) +
13149            (long) (tm2->ms - tm1->ms);
13150 }
13151
13152
13153 /*
13154  * Code to manage the game clocks.
13155  *
13156  * In tournament play, black starts the clock and then white makes a move.
13157  * We give the human user a slight advantage if he is playing white---the
13158  * clocks don't run until he makes his first move, so it takes zero time.
13159  * Also, we don't account for network lag, so we could get out of sync
13160  * with GNU Chess's clock -- but then, referees are always right.  
13161  */
13162
13163 static TimeMark tickStartTM;
13164 static long intendedTickLength;
13165
13166 long
13167 NextTickLength(timeRemaining)
13168      long timeRemaining;
13169 {
13170     long nominalTickLength, nextTickLength;
13171
13172     if (timeRemaining > 0L && timeRemaining <= 10000L)
13173       nominalTickLength = 100L;
13174     else
13175       nominalTickLength = 1000L;
13176     nextTickLength = timeRemaining % nominalTickLength;
13177     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13178
13179     return nextTickLength;
13180 }
13181
13182 /* Adjust clock one minute up or down */
13183 void
13184 AdjustClock(Boolean which, int dir)
13185 {
13186     if(which) blackTimeRemaining += 60000*dir;
13187     else      whiteTimeRemaining += 60000*dir;
13188     DisplayBothClocks();
13189 }
13190
13191 /* Stop clocks and reset to a fresh time control */
13192 void
13193 ResetClocks() 
13194 {
13195     (void) StopClockTimer();
13196     if (appData.icsActive) {
13197         whiteTimeRemaining = blackTimeRemaining = 0;
13198     } else { /* [HGM] correct new time quote for time odds */
13199         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13200         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13201     }
13202     if (whiteFlag || blackFlag) {
13203         DisplayTitle("");
13204         whiteFlag = blackFlag = FALSE;
13205     }
13206     DisplayBothClocks();
13207 }
13208
13209 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13210
13211 /* Decrement running clock by amount of time that has passed */
13212 void
13213 DecrementClocks()
13214 {
13215     long timeRemaining;
13216     long lastTickLength, fudge;
13217     TimeMark now;
13218
13219     if (!appData.clockMode) return;
13220     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13221         
13222     GetTimeMark(&now);
13223
13224     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13225
13226     /* Fudge if we woke up a little too soon */
13227     fudge = intendedTickLength - lastTickLength;
13228     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13229
13230     if (WhiteOnMove(forwardMostMove)) {
13231         if(whiteNPS >= 0) lastTickLength = 0;
13232         timeRemaining = whiteTimeRemaining -= lastTickLength;
13233         DisplayWhiteClock(whiteTimeRemaining - fudge,
13234                           WhiteOnMove(currentMove));
13235     } else {
13236         if(blackNPS >= 0) lastTickLength = 0;
13237         timeRemaining = blackTimeRemaining -= lastTickLength;
13238         DisplayBlackClock(blackTimeRemaining - fudge,
13239                           !WhiteOnMove(currentMove));
13240     }
13241
13242     if (CheckFlags()) return;
13243         
13244     tickStartTM = now;
13245     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13246     StartClockTimer(intendedTickLength);
13247
13248     /* if the time remaining has fallen below the alarm threshold, sound the
13249      * alarm. if the alarm has sounded and (due to a takeback or time control
13250      * with increment) the time remaining has increased to a level above the
13251      * threshold, reset the alarm so it can sound again. 
13252      */
13253     
13254     if (appData.icsActive && appData.icsAlarm) {
13255
13256         /* make sure we are dealing with the user's clock */
13257         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13258                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13259            )) return;
13260
13261         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13262             alarmSounded = FALSE;
13263         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13264             PlayAlarmSound();
13265             alarmSounded = TRUE;
13266         }
13267     }
13268 }
13269
13270
13271 /* A player has just moved, so stop the previously running
13272    clock and (if in clock mode) start the other one.
13273    We redisplay both clocks in case we're in ICS mode, because
13274    ICS gives us an update to both clocks after every move.
13275    Note that this routine is called *after* forwardMostMove
13276    is updated, so the last fractional tick must be subtracted
13277    from the color that is *not* on move now.
13278 */
13279 void
13280 SwitchClocks()
13281 {
13282     long lastTickLength;
13283     TimeMark now;
13284     int flagged = FALSE;
13285
13286     GetTimeMark(&now);
13287
13288     if (StopClockTimer() && appData.clockMode) {
13289         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13290         if (WhiteOnMove(forwardMostMove)) {
13291             if(blackNPS >= 0) lastTickLength = 0;
13292             blackTimeRemaining -= lastTickLength;
13293            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13294 //         if(pvInfoList[forwardMostMove-1].time == -1)
13295                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13296                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13297         } else {
13298            if(whiteNPS >= 0) lastTickLength = 0;
13299            whiteTimeRemaining -= lastTickLength;
13300            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13301 //         if(pvInfoList[forwardMostMove-1].time == -1)
13302                  pvInfoList[forwardMostMove-1].time = 
13303                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13304         }
13305         flagged = CheckFlags();
13306     }
13307     CheckTimeControl();
13308
13309     if (flagged || !appData.clockMode) return;
13310
13311     switch (gameMode) {
13312       case MachinePlaysBlack:
13313       case MachinePlaysWhite:
13314       case BeginningOfGame:
13315         if (pausing) return;
13316         break;
13317
13318       case EditGame:
13319       case PlayFromGameFile:
13320       case IcsExamining:
13321         return;
13322
13323       default:
13324         break;
13325     }
13326
13327     tickStartTM = now;
13328     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13329       whiteTimeRemaining : blackTimeRemaining);
13330     StartClockTimer(intendedTickLength);
13331 }
13332         
13333
13334 /* Stop both clocks */
13335 void
13336 StopClocks()
13337 {       
13338     long lastTickLength;
13339     TimeMark now;
13340
13341     if (!StopClockTimer()) return;
13342     if (!appData.clockMode) return;
13343
13344     GetTimeMark(&now);
13345
13346     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13347     if (WhiteOnMove(forwardMostMove)) {
13348         if(whiteNPS >= 0) lastTickLength = 0;
13349         whiteTimeRemaining -= lastTickLength;
13350         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13351     } else {
13352         if(blackNPS >= 0) lastTickLength = 0;
13353         blackTimeRemaining -= lastTickLength;
13354         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13355     }
13356     CheckFlags();
13357 }
13358         
13359 /* Start clock of player on move.  Time may have been reset, so
13360    if clock is already running, stop and restart it. */
13361 void
13362 StartClocks()
13363 {
13364     (void) StopClockTimer(); /* in case it was running already */
13365     DisplayBothClocks();
13366     if (CheckFlags()) return;
13367
13368     if (!appData.clockMode) return;
13369     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13370
13371     GetTimeMark(&tickStartTM);
13372     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13373       whiteTimeRemaining : blackTimeRemaining);
13374
13375    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13376     whiteNPS = blackNPS = -1; 
13377     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13378        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13379         whiteNPS = first.nps;
13380     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13381        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13382         blackNPS = first.nps;
13383     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13384         whiteNPS = second.nps;
13385     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13386         blackNPS = second.nps;
13387     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13388
13389     StartClockTimer(intendedTickLength);
13390 }
13391
13392 char *
13393 TimeString(ms)
13394      long ms;
13395 {
13396     long second, minute, hour, day;
13397     char *sign = "";
13398     static char buf[32];
13399     
13400     if (ms > 0 && ms <= 9900) {
13401       /* convert milliseconds to tenths, rounding up */
13402       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13403
13404       sprintf(buf, " %03.1f ", tenths/10.0);
13405       return buf;
13406     }
13407
13408     /* convert milliseconds to seconds, rounding up */
13409     /* use floating point to avoid strangeness of integer division
13410        with negative dividends on many machines */
13411     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13412
13413     if (second < 0) {
13414         sign = "-";
13415         second = -second;
13416     }
13417     
13418     day = second / (60 * 60 * 24);
13419     second = second % (60 * 60 * 24);
13420     hour = second / (60 * 60);
13421     second = second % (60 * 60);
13422     minute = second / 60;
13423     second = second % 60;
13424     
13425     if (day > 0)
13426       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13427               sign, day, hour, minute, second);
13428     else if (hour > 0)
13429       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13430     else
13431       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13432     
13433     return buf;
13434 }
13435
13436
13437 /*
13438  * This is necessary because some C libraries aren't ANSI C compliant yet.
13439  */
13440 char *
13441 StrStr(string, match)
13442      char *string, *match;
13443 {
13444     int i, length;
13445     
13446     length = strlen(match);
13447     
13448     for (i = strlen(string) - length; i >= 0; i--, string++)
13449       if (!strncmp(match, string, length))
13450         return string;
13451     
13452     return NULL;
13453 }
13454
13455 char *
13456 StrCaseStr(string, match)
13457      char *string, *match;
13458 {
13459     int i, j, length;
13460     
13461     length = strlen(match);
13462     
13463     for (i = strlen(string) - length; i >= 0; i--, string++) {
13464         for (j = 0; j < length; j++) {
13465             if (ToLower(match[j]) != ToLower(string[j]))
13466               break;
13467         }
13468         if (j == length) return string;
13469     }
13470
13471     return NULL;
13472 }
13473
13474 #ifndef _amigados
13475 int
13476 StrCaseCmp(s1, s2)
13477      char *s1, *s2;
13478 {
13479     char c1, c2;
13480     
13481     for (;;) {
13482         c1 = ToLower(*s1++);
13483         c2 = ToLower(*s2++);
13484         if (c1 > c2) return 1;
13485         if (c1 < c2) return -1;
13486         if (c1 == NULLCHAR) return 0;
13487     }
13488 }
13489
13490
13491 int
13492 ToLower(c)
13493      int c;
13494 {
13495     return isupper(c) ? tolower(c) : c;
13496 }
13497
13498
13499 int
13500 ToUpper(c)
13501      int c;
13502 {
13503     return islower(c) ? toupper(c) : c;
13504 }
13505 #endif /* !_amigados    */
13506
13507 char *
13508 StrSave(s)
13509      char *s;
13510 {
13511     char *ret;
13512
13513     if ((ret = (char *) malloc(strlen(s) + 1))) {
13514         strcpy(ret, s);
13515     }
13516     return ret;
13517 }
13518
13519 char *
13520 StrSavePtr(s, savePtr)
13521      char *s, **savePtr;
13522 {
13523     if (*savePtr) {
13524         free(*savePtr);
13525     }
13526     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13527         strcpy(*savePtr, s);
13528     }
13529     return(*savePtr);
13530 }
13531
13532 char *
13533 PGNDate()
13534 {
13535     time_t clock;
13536     struct tm *tm;
13537     char buf[MSG_SIZ];
13538
13539     clock = time((time_t *)NULL);
13540     tm = localtime(&clock);
13541     sprintf(buf, "%04d.%02d.%02d",
13542             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13543     return StrSave(buf);
13544 }
13545
13546
13547 char *
13548 PositionToFEN(move, overrideCastling)
13549      int move;
13550      char *overrideCastling;
13551 {
13552     int i, j, fromX, fromY, toX, toY;
13553     int whiteToPlay;
13554     char buf[128];
13555     char *p, *q;
13556     int emptycount;
13557     ChessSquare piece;
13558
13559     whiteToPlay = (gameMode == EditPosition) ?
13560       !blackPlaysFirst : (move % 2 == 0);
13561     p = buf;
13562
13563     /* Piece placement data */
13564     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13565         emptycount = 0;
13566         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13567             if (boards[move][i][j] == EmptySquare) {
13568                 emptycount++;
13569             } else { ChessSquare piece = boards[move][i][j];
13570                 if (emptycount > 0) {
13571                     if(emptycount<10) /* [HGM] can be >= 10 */
13572                         *p++ = '0' + emptycount;
13573                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13574                     emptycount = 0;
13575                 }
13576                 if(PieceToChar(piece) == '+') {
13577                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13578                     *p++ = '+';
13579                     piece = (ChessSquare)(DEMOTED piece);
13580                 } 
13581                 *p++ = PieceToChar(piece);
13582                 if(p[-1] == '~') {
13583                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13584                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13585                     *p++ = '~';
13586                 }
13587             }
13588         }
13589         if (emptycount > 0) {
13590             if(emptycount<10) /* [HGM] can be >= 10 */
13591                 *p++ = '0' + emptycount;
13592             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13593             emptycount = 0;
13594         }
13595         *p++ = '/';
13596     }
13597     *(p - 1) = ' ';
13598
13599     /* [HGM] print Crazyhouse or Shogi holdings */
13600     if( gameInfo.holdingsWidth ) {
13601         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13602         q = p;
13603         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13604             piece = boards[move][i][BOARD_WIDTH-1];
13605             if( piece != EmptySquare )
13606               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13607                   *p++ = PieceToChar(piece);
13608         }
13609         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13610             piece = boards[move][BOARD_HEIGHT-i-1][0];
13611             if( piece != EmptySquare )
13612               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13613                   *p++ = PieceToChar(piece);
13614         }
13615
13616         if( q == p ) *p++ = '-';
13617         *p++ = ']';
13618         *p++ = ' ';
13619     }
13620
13621     /* Active color */
13622     *p++ = whiteToPlay ? 'w' : 'b';
13623     *p++ = ' ';
13624
13625   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13626     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13627   } else {
13628   if(nrCastlingRights) {
13629      q = p;
13630      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13631        /* [HGM] write directly from rights */
13632            if(castlingRights[move][2] >= 0 &&
13633               castlingRights[move][0] >= 0   )
13634                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13635            if(castlingRights[move][2] >= 0 &&
13636               castlingRights[move][1] >= 0   )
13637                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13638            if(castlingRights[move][5] >= 0 &&
13639               castlingRights[move][3] >= 0   )
13640                 *p++ = castlingRights[move][3] + AAA;
13641            if(castlingRights[move][5] >= 0 &&
13642               castlingRights[move][4] >= 0   )
13643                 *p++ = castlingRights[move][4] + AAA;
13644      } else {
13645
13646         /* [HGM] write true castling rights */
13647         if( nrCastlingRights == 6 ) {
13648             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13649                castlingRights[move][2] >= 0  ) *p++ = 'K';
13650             if(castlingRights[move][1] == BOARD_LEFT &&
13651                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13652             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13653                castlingRights[move][5] >= 0  ) *p++ = 'k';
13654             if(castlingRights[move][4] == BOARD_LEFT &&
13655                castlingRights[move][5] >= 0  ) *p++ = 'q';
13656         }
13657      }
13658      if (q == p) *p++ = '-'; /* No castling rights */
13659      *p++ = ' ';
13660   }
13661
13662   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13663      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13664     /* En passant target square */
13665     if (move > backwardMostMove) {
13666         fromX = moveList[move - 1][0] - AAA;
13667         fromY = moveList[move - 1][1] - ONE;
13668         toX = moveList[move - 1][2] - AAA;
13669         toY = moveList[move - 1][3] - ONE;
13670         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13671             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13672             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13673             fromX == toX) {
13674             /* 2-square pawn move just happened */
13675             *p++ = toX + AAA;
13676             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13677         } else {
13678             *p++ = '-';
13679         }
13680     } else if(move == backwardMostMove) {
13681         // [HGM] perhaps we should always do it like this, and forget the above?
13682         if(epStatus[move] >= 0) {
13683             *p++ = epStatus[move] + AAA;
13684             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13685         } else {
13686             *p++ = '-';
13687         }
13688     } else {
13689         *p++ = '-';
13690     }
13691     *p++ = ' ';
13692   }
13693   }
13694
13695     /* [HGM] find reversible plies */
13696     {   int i = 0, j=move;
13697
13698         if (appData.debugMode) { int k;
13699             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13700             for(k=backwardMostMove; k<=forwardMostMove; k++)
13701                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13702
13703         }
13704
13705         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13706         if( j == backwardMostMove ) i += initialRulePlies;
13707         sprintf(p, "%d ", i);
13708         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13709     }
13710     /* Fullmove number */
13711     sprintf(p, "%d", (move / 2) + 1);
13712     
13713     return StrSave(buf);
13714 }
13715
13716 Boolean
13717 ParseFEN(board, blackPlaysFirst, fen)
13718     Board board;
13719      int *blackPlaysFirst;
13720      char *fen;
13721 {
13722     int i, j;
13723     char *p;
13724     int emptycount;
13725     ChessSquare piece;
13726
13727     p = fen;
13728
13729     /* [HGM] by default clear Crazyhouse holdings, if present */
13730     if(gameInfo.holdingsWidth) {
13731        for(i=0; i<BOARD_HEIGHT; i++) {
13732            board[i][0]             = EmptySquare; /* black holdings */
13733            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13734            board[i][1]             = (ChessSquare) 0; /* black counts */
13735            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13736        }
13737     }
13738
13739     /* Piece placement data */
13740     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13741         j = 0;
13742         for (;;) {
13743             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13744                 if (*p == '/') p++;
13745                 emptycount = gameInfo.boardWidth - j;
13746                 while (emptycount--)
13747                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13748                 break;
13749 #if(BOARD_SIZE >= 10)
13750             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13751                 p++; emptycount=10;
13752                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13753                 while (emptycount--)
13754                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13755 #endif
13756             } else if (isdigit(*p)) {
13757                 emptycount = *p++ - '0';
13758                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13759                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13760                 while (emptycount--)
13761                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13762             } else if (*p == '+' || isalpha(*p)) {
13763                 if (j >= gameInfo.boardWidth) return FALSE;
13764                 if(*p=='+') {
13765                     piece = CharToPiece(*++p);
13766                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13767                     piece = (ChessSquare) (PROMOTED piece ); p++;
13768                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13769                 } else piece = CharToPiece(*p++);
13770
13771                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13772                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13773                     piece = (ChessSquare) (PROMOTED piece);
13774                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13775                     p++;
13776                 }
13777                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13778             } else {
13779                 return FALSE;
13780             }
13781         }
13782     }
13783     while (*p == '/' || *p == ' ') p++;
13784
13785     /* [HGM] look for Crazyhouse holdings here */
13786     while(*p==' ') p++;
13787     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13788         if(*p == '[') p++;
13789         if(*p == '-' ) *p++; /* empty holdings */ else {
13790             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13791             /* if we would allow FEN reading to set board size, we would   */
13792             /* have to add holdings and shift the board read so far here   */
13793             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13794                 *p++;
13795                 if((int) piece >= (int) BlackPawn ) {
13796                     i = (int)piece - (int)BlackPawn;
13797                     i = PieceToNumber((ChessSquare)i);
13798                     if( i >= gameInfo.holdingsSize ) return FALSE;
13799                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13800                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13801                 } else {
13802                     i = (int)piece - (int)WhitePawn;
13803                     i = PieceToNumber((ChessSquare)i);
13804                     if( i >= gameInfo.holdingsSize ) return FALSE;
13805                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13806                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13807                 }
13808             }
13809         }
13810         if(*p == ']') *p++;
13811     }
13812
13813     while(*p == ' ') p++;
13814
13815     /* Active color */
13816     switch (*p++) {
13817       case 'w':
13818         *blackPlaysFirst = FALSE;
13819         break;
13820       case 'b': 
13821         *blackPlaysFirst = TRUE;
13822         break;
13823       default:
13824         return FALSE;
13825     }
13826
13827     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13828     /* return the extra info in global variiables             */
13829
13830     /* set defaults in case FEN is incomplete */
13831     FENepStatus = EP_UNKNOWN;
13832     for(i=0; i<nrCastlingRights; i++ ) {
13833         FENcastlingRights[i] =
13834             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13835     }   /* assume possible unless obviously impossible */
13836     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13837     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13838     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13839     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13840     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13841     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13842     FENrulePlies = 0;
13843
13844     while(*p==' ') p++;
13845     if(nrCastlingRights) {
13846       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13847           /* castling indicator present, so default becomes no castlings */
13848           for(i=0; i<nrCastlingRights; i++ ) {
13849                  FENcastlingRights[i] = -1;
13850           }
13851       }
13852       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13853              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13854              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13855              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13856         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13857
13858         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13859             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13860             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13861         }
13862         switch(c) {
13863           case'K':
13864               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13865               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13866               FENcastlingRights[2] = whiteKingFile;
13867               break;
13868           case'Q':
13869               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13870               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13871               FENcastlingRights[2] = whiteKingFile;
13872               break;
13873           case'k':
13874               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13875               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13876               FENcastlingRights[5] = blackKingFile;
13877               break;
13878           case'q':
13879               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13880               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13881               FENcastlingRights[5] = blackKingFile;
13882           case '-':
13883               break;
13884           default: /* FRC castlings */
13885               if(c >= 'a') { /* black rights */
13886                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13887                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13888                   if(i == BOARD_RGHT) break;
13889                   FENcastlingRights[5] = i;
13890                   c -= AAA;
13891                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13892                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13893                   if(c > i)
13894                       FENcastlingRights[3] = c;
13895                   else
13896                       FENcastlingRights[4] = c;
13897               } else { /* white rights */
13898                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13899                     if(board[0][i] == WhiteKing) break;
13900                   if(i == BOARD_RGHT) break;
13901                   FENcastlingRights[2] = i;
13902                   c -= AAA - 'a' + 'A';
13903                   if(board[0][c] >= WhiteKing) break;
13904                   if(c > i)
13905                       FENcastlingRights[0] = c;
13906                   else
13907                       FENcastlingRights[1] = c;
13908               }
13909         }
13910       }
13911     if (appData.debugMode) {
13912         fprintf(debugFP, "FEN castling rights:");
13913         for(i=0; i<nrCastlingRights; i++)
13914         fprintf(debugFP, " %d", FENcastlingRights[i]);
13915         fprintf(debugFP, "\n");
13916     }
13917
13918       while(*p==' ') p++;
13919     }
13920
13921     /* read e.p. field in games that know e.p. capture */
13922     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13923        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13924       if(*p=='-') {
13925         p++; FENepStatus = EP_NONE;
13926       } else {
13927          char c = *p++ - AAA;
13928
13929          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13930          if(*p >= '0' && *p <='9') *p++;
13931          FENepStatus = c;
13932       }
13933     }
13934
13935
13936     if(sscanf(p, "%d", &i) == 1) {
13937         FENrulePlies = i; /* 50-move ply counter */
13938         /* (The move number is still ignored)    */
13939     }
13940
13941     return TRUE;
13942 }
13943       
13944 void
13945 EditPositionPasteFEN(char *fen)
13946 {
13947   if (fen != NULL) {
13948     Board initial_position;
13949
13950     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13951       DisplayError(_("Bad FEN position in clipboard"), 0);
13952       return ;
13953     } else {
13954       int savedBlackPlaysFirst = blackPlaysFirst;
13955       EditPositionEvent();
13956       blackPlaysFirst = savedBlackPlaysFirst;
13957       CopyBoard(boards[0], initial_position);
13958           /* [HGM] copy FEN attributes as well */
13959           {   int i;
13960               initialRulePlies = FENrulePlies;
13961               epStatus[0] = FENepStatus;
13962               for( i=0; i<nrCastlingRights; i++ )
13963                   castlingRights[0][i] = FENcastlingRights[i];
13964           }
13965       EditPositionDone();
13966       DisplayBothClocks();
13967       DrawPosition(FALSE, boards[currentMove]);
13968     }
13969   }
13970 }