9a95466dcc20a5f2eb65f74b03bc7536d5ac62de
[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((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     set_cont_sequence(appData.wrapContSeq);
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908         return; // prevent overwriting by pre-board holdings
1909
1910     if( (int)lowestPiece >= BlackPawn ) {
1911         holdingsColumn = 0;
1912         countsColumn = 1;
1913         holdingsStartRow = BOARD_HEIGHT-1;
1914         direction = -1;
1915     } else {
1916         holdingsColumn = BOARD_WIDTH-1;
1917         countsColumn = BOARD_WIDTH-2;
1918         holdingsStartRow = 0;
1919         direction = 1;
1920     }
1921
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923         board[i][holdingsColumn] = EmptySquare;
1924         board[i][countsColumn]   = (ChessSquare) 0;
1925     }
1926     while( (p=*holdings++) != NULLCHAR ) {
1927         piece = CharToPiece( ToUpper(p) );
1928         if(piece == EmptySquare) continue;
1929         /*j = (int) piece - (int) WhitePawn;*/
1930         j = PieceToNumber(piece);
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932         if(j < 0) continue;               /* should not happen */
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935         board[holdingsStartRow+j*direction][countsColumn]++;
1936     }
1937 }
1938
1939
1940 void
1941 VariantSwitch(Board board, VariantClass newVariant)
1942 {
1943    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944    Board oldBoard;
1945
1946    startedFromPositionFile = FALSE;
1947    if(gameInfo.variant == newVariant) return;
1948
1949    /* [HGM] This routine is called each time an assignment is made to
1950     * gameInfo.variant during a game, to make sure the board sizes
1951     * are set to match the new variant. If that means adding or deleting
1952     * holdings, we shift the playing board accordingly
1953     * This kludge is needed because in ICS observe mode, we get boards
1954     * of an ongoing game without knowing the variant, and learn about the
1955     * latter only later. This can be because of the move list we requested,
1956     * in which case the game history is refilled from the beginning anyway,
1957     * but also when receiving holdings of a crazyhouse game. In the latter
1958     * case we want to add those holdings to the already received position.
1959     */
1960
1961    
1962    if (appData.debugMode) {
1963      fprintf(debugFP, "Switch board from %s to %s\n",
1964              VariantName(gameInfo.variant), VariantName(newVariant));
1965      setbuf(debugFP, NULL);
1966    }
1967    shuffleOpenings = 0;       /* [HGM] shuffle */
1968    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969    switch(newVariant) 
1970      {
1971      case VariantShogi:
1972        newWidth = 9;  newHeight = 9;
1973        gameInfo.holdingsSize = 7;
1974      case VariantBughouse:
1975      case VariantCrazyhouse:
1976        newHoldingsWidth = 2; break;
1977      case VariantGreat:
1978        newWidth = 10;
1979      case VariantSuper:
1980        newHoldingsWidth = 2;
1981        gameInfo.holdingsSize = 8;
1982        break;
1983      case VariantGothic:
1984      case VariantCapablanca:
1985      case VariantCapaRandom:
1986        newWidth = 10;
1987      default:
1988        newHoldingsWidth = gameInfo.holdingsSize = 0;
1989      };
1990    
1991    if(newWidth  != gameInfo.boardWidth  ||
1992       newHeight != gameInfo.boardHeight ||
1993       newHoldingsWidth != gameInfo.holdingsWidth ) {
1994      
1995      /* shift position to new playing area, if needed */
1996      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997        for(i=0; i<BOARD_HEIGHT; i++) 
1998          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000              board[i][j];
2001        for(i=0; i<newHeight; i++) {
2002          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004        }
2005      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006        for(i=0; i<BOARD_HEIGHT; i++)
2007          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009              board[i][j];
2010      }
2011      gameInfo.boardWidth  = newWidth;
2012      gameInfo.boardHeight = newHeight;
2013      gameInfo.holdingsWidth = newHoldingsWidth;
2014      gameInfo.variant = newVariant;
2015      InitDrawingSizes(-2, 0);
2016    } else gameInfo.variant = newVariant;
2017    CopyBoard(oldBoard, board);   // remember correctly formatted board
2018      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2019    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2020 }
2021
2022 static int loggedOn = FALSE;
2023
2024 /*-- Game start info cache: --*/
2025 int gs_gamenum;
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\   ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2033
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2036
2037 void
2038 read_from_ics(isr, closure, data, count, error)
2039      InputSourceRef isr;
2040      VOIDSTAR closure;
2041      char *data;
2042      int count;
2043      int error;
2044 {
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2054     
2055     static int started = STARTED_NONE;
2056     static char parse[20000];
2057     static int parse_pos = 0;
2058     static char buf[BUF_SIZE + 1];
2059     static int firstTime = TRUE, intfSet = FALSE;
2060     static ColorClass prevColor = ColorNormal;
2061     static int savingComment = FALSE;
2062     static int cmatch = 0; // continuation sequence match
2063     char *bp;
2064     char str[500];
2065     int i, oldi;
2066     int buf_len;
2067     int next_out;
2068     int tkind;
2069     int backup;    /* [DM] For zippy color lines */
2070     char *p;
2071     char talker[MSG_SIZ]; // [HGM] chat
2072     int channel;
2073
2074     if (appData.debugMode) {
2075       if (!error) {
2076         fprintf(debugFP, "<ICS: ");
2077         show_bytes(debugFP, data, count);
2078         fprintf(debugFP, "\n");
2079       }
2080     }
2081
2082     if (appData.debugMode) { int f = forwardMostMove;
2083         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085     }
2086     if (count > 0) {
2087         /* If last read ended with a partial line that we couldn't parse,
2088            prepend it to the new read and try again. */
2089         if (leftover_len > 0) {
2090             for (i=0; i<leftover_len; i++)
2091               buf[i] = buf[leftover_start + i];
2092         }
2093
2094     /* copy new characters into the buffer */
2095     bp = buf + leftover_len;
2096     buf_len=leftover_len;
2097     for (i=0; i<count; i++)
2098     {
2099         // ignore these
2100         if (data[i] == '\r')
2101             continue;
2102
2103         // join lines split by ICS?
2104         if (!appData.noJoin)
2105         {
2106             /*
2107                 Joining just consists of finding matches against the
2108                 continuation sequence, and discarding that sequence
2109                 if found instead of copying it.  So, until a match
2110                 fails, there's nothing to do since it might be the
2111                 complete sequence, and thus, something we don't want
2112                 copied.
2113             */
2114             if (data[i] == cont_seq[cmatch])
2115             {
2116                 cmatch++;
2117                 if (cmatch == strlen(cont_seq))
2118                 {
2119                     cmatch = 0; // complete match.  just reset the counter
2120
2121                     /*
2122                         it's possible for the ICS to not include the space
2123                         at the end of the last word, making our [correct]
2124                         join operation fuse two separate words.  the server
2125                         does this when the space occurs at the width setting.
2126                     */
2127                     if (!buf_len || buf[buf_len-1] != ' ')
2128                     {
2129                         *bp++ = ' ';
2130                         buf_len++;
2131                     }
2132                 }
2133                 continue;
2134             }
2135             else if (cmatch)
2136             {
2137                 /*
2138                     match failed, so we have to copy what matched before
2139                     falling through and copying this character.  In reality,
2140                     this will only ever be just the newline character, but
2141                     it doesn't hurt to be precise.
2142                 */
2143                 strncpy(bp, cont_seq, cmatch);
2144                 bp += cmatch;
2145                 buf_len += cmatch;
2146                 cmatch = 0;
2147             }
2148         }
2149
2150         // copy this char
2151         *bp++ = data[i];
2152         buf_len++;
2153     }
2154
2155         buf[buf_len] = NULLCHAR;
2156         next_out = leftover_len;
2157         leftover_start = 0;
2158         
2159         i = 0;
2160         while (i < buf_len) {
2161             /* Deal with part of the TELNET option negotiation
2162                protocol.  We refuse to do anything beyond the
2163                defaults, except that we allow the WILL ECHO option,
2164                which ICS uses to turn off password echoing when we are
2165                directly connected to it.  We reject this option
2166                if localLineEditing mode is on (always on in xboard)
2167                and we are talking to port 23, which might be a real
2168                telnet server that will try to keep WILL ECHO on permanently.
2169              */
2170             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172                 unsigned char option;
2173                 oldi = i;
2174                 switch ((unsigned char) buf[++i]) {
2175                   case TN_WILL:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<WILL ");
2178                     switch (option = (unsigned char) buf[++i]) {
2179                       case TN_ECHO:
2180                         if (appData.debugMode)
2181                           fprintf(debugFP, "ECHO ");
2182                         /* Reply only if this is a change, according
2183                            to the protocol rules. */
2184                         if (remoteEchoOption) break;
2185                         if (appData.localLineEditing &&
2186                             atoi(appData.icsPort) == TN_PORT) {
2187                             TelnetRequest(TN_DONT, TN_ECHO);
2188                         } else {
2189                             EchoOff();
2190                             TelnetRequest(TN_DO, TN_ECHO);
2191                             remoteEchoOption = TRUE;
2192                         }
2193                         break;
2194                       default:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "%d ", option);
2197                         /* Whatever this is, we don't want it. */
2198                         TelnetRequest(TN_DONT, option);
2199                         break;
2200                     }
2201                     break;
2202                   case TN_WONT:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<WONT ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       case TN_ECHO:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "ECHO ");
2209                         /* Reply only if this is a change, according
2210                            to the protocol rules. */
2211                         if (!remoteEchoOption) break;
2212                         EchoOn();
2213                         TelnetRequest(TN_DONT, TN_ECHO);
2214                         remoteEchoOption = FALSE;
2215                         break;
2216                       default:
2217                         if (appData.debugMode)
2218                           fprintf(debugFP, "%d ", (unsigned char) option);
2219                         /* Whatever this is, it must already be turned
2220                            off, because we never agree to turn on
2221                            anything non-default, so according to the
2222                            protocol rules, we don't reply. */
2223                         break;
2224                     }
2225                     break;
2226                   case TN_DO:
2227                     if (appData.debugMode)
2228                       fprintf(debugFP, "\n<DO ");
2229                     switch (option = (unsigned char) buf[++i]) {
2230                       default:
2231                         /* Whatever this is, we refuse to do it. */
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", option);
2234                         TelnetRequest(TN_WONT, option);
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DONT:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DONT ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         if (appData.debugMode)
2244                           fprintf(debugFP, "%d ", option);
2245                         /* Whatever this is, we are already not doing
2246                            it, because we never agree to do anything
2247                            non-default, so according to the protocol
2248                            rules, we don't reply. */
2249                         break;
2250                     }
2251                     break;
2252                   case TN_IAC:
2253                     if (appData.debugMode)
2254                       fprintf(debugFP, "\n<IAC ");
2255                     /* Doubled IAC; pass it through */
2256                     i--;
2257                     break;
2258                   default:
2259                     if (appData.debugMode)
2260                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261                     /* Drop all other telnet commands on the floor */
2262                     break;
2263                 }
2264                 if (oldi > next_out)
2265                   SendToPlayer(&buf[next_out], oldi - next_out);
2266                 if (++i > next_out)
2267                   next_out = i;
2268                 continue;
2269             }
2270                 
2271             /* OK, this at least will *usually* work */
2272             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273                 loggedOn = TRUE;
2274             }
2275             
2276             if (loggedOn && !intfSet) {
2277                 if (ics_type == ICS_ICC) {
2278                   sprintf(str,
2279                           "/set-quietly interface %s\n/set-quietly style 12\n",
2280                           programVersion);
2281                 } else if (ics_type == ICS_CHESSNET) {
2282                   sprintf(str, "/style 12\n");
2283                 } else {
2284                   strcpy(str, "alias $ @\n$set interface ");
2285                   strcat(str, programVersion);
2286                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2287 #ifdef WIN32
2288                   strcat(str, "$iset nohighlight 1\n");
2289 #endif
2290                   strcat(str, "$iset lock 1\n$style 12\n");
2291                 }
2292                 SendToICS(str);
2293                 NotifyFrontendLogin();
2294                 intfSet = TRUE;
2295             }
2296
2297             if (started == STARTED_COMMENT) {
2298                 /* Accumulate characters in comment */
2299                 parse[parse_pos++] = buf[i];
2300                 if (buf[i] == '\n') {
2301                     parse[parse_pos] = NULLCHAR;
2302                     if(chattingPartner>=0) {
2303                         char mess[MSG_SIZ];
2304                         sprintf(mess, "%s%s", talker, parse);
2305                         OutputChatMessage(chattingPartner, mess);
2306                         chattingPartner = -1;
2307                     } else
2308                     if(!suppressKibitz) // [HGM] kibitz
2309                         AppendComment(forwardMostMove, StripHighlight(parse));
2310                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311                         int nrDigit = 0, nrAlph = 0, i;
2312                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314                         parse[parse_pos] = NULLCHAR;
2315                         // try to be smart: if it does not look like search info, it should go to
2316                         // ICS interaction window after all, not to engine-output window.
2317                         for(i=0; i<parse_pos; i++) { // count letters and digits
2318                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2320                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2321                         }
2322                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323                             int depth=0; float score;
2324                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326                                 pvInfoList[forwardMostMove-1].depth = depth;
2327                                 pvInfoList[forwardMostMove-1].score = 100*score;
2328                             }
2329                             OutputKibitz(suppressKibitz, parse);
2330                         } else {
2331                             char tmp[MSG_SIZ];
2332                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333                             SendToPlayer(tmp, strlen(tmp));
2334                         }
2335                     }
2336                     started = STARTED_NONE;
2337                 } else {
2338                     /* Don't match patterns against characters in chatter */
2339                     i++;
2340                     continue;
2341                 }
2342             }
2343             if (started == STARTED_CHATTER) {
2344                 if (buf[i] != '\n') {
2345                     /* Don't match patterns against characters in chatter */
2346                     i++;
2347                     continue;
2348                 }
2349                 started = STARTED_NONE;
2350             }
2351
2352             /* Kludge to deal with rcmd protocol */
2353             if (firstTime && looking_at(buf, &i, "\001*")) {
2354                 DisplayFatalError(&buf[1], 0, 1);
2355                 continue;
2356             } else {
2357                 firstTime = FALSE;
2358             }
2359
2360             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361                 ics_type = ICS_ICC;
2362                 ics_prefix = "/";
2363                 if (appData.debugMode)
2364                   fprintf(debugFP, "ics_type %d\n", ics_type);
2365                 continue;
2366             }
2367             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368                 ics_type = ICS_FICS;
2369                 ics_prefix = "$";
2370                 if (appData.debugMode)
2371                   fprintf(debugFP, "ics_type %d\n", ics_type);
2372                 continue;
2373             }
2374             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375                 ics_type = ICS_CHESSNET;
2376                 ics_prefix = "/";
2377                 if (appData.debugMode)
2378                   fprintf(debugFP, "ics_type %d\n", ics_type);
2379                 continue;
2380             }
2381
2382             if (!loggedOn &&
2383                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2385                  looking_at(buf, &i, "will be \"*\""))) {
2386               strcpy(ics_handle, star_match[0]);
2387               continue;
2388             }
2389
2390             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2391               char buf[MSG_SIZ];
2392               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393               DisplayIcsInteractionTitle(buf);
2394               have_set_title = TRUE;
2395             }
2396
2397             /* skip finger notes */
2398             if (started == STARTED_NONE &&
2399                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400                  (buf[i] == '1' && buf[i+1] == '0')) &&
2401                 buf[i+2] == ':' && buf[i+3] == ' ') {
2402               started = STARTED_CHATTER;
2403               i += 3;
2404               continue;
2405             }
2406
2407             /* skip formula vars */
2408             if (started == STARTED_NONE &&
2409                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410               started = STARTED_CHATTER;
2411               i += 3;
2412               continue;
2413             }
2414
2415             oldi = i;
2416             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417             if (appData.autoKibitz && started == STARTED_NONE && 
2418                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2419                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420                 if(looking_at(buf, &i, "* kibitzes: ") &&
2421                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2422                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2423                         suppressKibitz = TRUE;
2424                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425                                 && (gameMode == IcsPlayingWhite)) ||
2426                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2428                             started = STARTED_CHATTER; // own kibitz we simply discard
2429                         else {
2430                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431                             parse_pos = 0; parse[0] = NULLCHAR;
2432                             savingComment = TRUE;
2433                             suppressKibitz = gameMode != IcsObserving ? 2 :
2434                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435                         } 
2436                         continue;
2437                 } else
2438                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439                     started = STARTED_CHATTER;
2440                     suppressKibitz = TRUE;
2441                 }
2442             } // [HGM] kibitz: end of patch
2443
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2445
2446             // [HGM] chat: intercept tells by users for which we have an open chat window
2447             channel = -1;
2448             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2449                                            looking_at(buf, &i, "* whispers:") ||
2450                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2452                 int p;
2453                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454                 chattingPartner = -1;
2455
2456                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457                 for(p=0; p<MAX_CHAT; p++) {
2458                     if(channel == atoi(chatPartner[p])) {
2459                     talker[0] = '['; strcat(talker, "]");
2460                     chattingPartner = p; break;
2461                     }
2462                 } else
2463                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464                 for(p=0; p<MAX_CHAT; p++) {
2465                     if(!strcmp("WHISPER", chatPartner[p])) {
2466                         talker[0] = '['; strcat(talker, "]");
2467                         chattingPartner = p; break;
2468                     }
2469                 }
2470                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2472                     talker[0] = 0;
2473                     chattingPartner = p; break;
2474                 }
2475                 if(chattingPartner<0) i = oldi; else {
2476                     started = STARTED_COMMENT;
2477                     parse_pos = 0; parse[0] = NULLCHAR;
2478                     savingComment = TRUE;
2479                     suppressKibitz = TRUE;
2480                 }
2481             } // [HGM] chat: end of patch
2482
2483             if (appData.zippyTalk || appData.zippyPlay) {
2484                 /* [DM] Backup address for color zippy lines */
2485                 backup = i;
2486 #if ZIPPY
2487        #ifdef WIN32
2488                if (loggedOn == TRUE)
2489                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2491        #else
2492                 if (ZippyControl(buf, &i) ||
2493                     ZippyConverse(buf, &i) ||
2494                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2495                       loggedOn = TRUE;
2496                       if (!appData.colorize) continue;
2497                 }
2498        #endif
2499 #endif
2500             } // [DM] 'else { ' deleted
2501                 if (
2502                     /* Regular tells and says */
2503                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2505                     looking_at(buf, &i, "* says: ") ||
2506                     /* Don't color "message" or "messages" output */
2507                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508                     looking_at(buf, &i, "*. * at *:*: ") ||
2509                     looking_at(buf, &i, "--* (*:*): ") ||
2510                     /* Message notifications (same color as tells) */
2511                     looking_at(buf, &i, "* has left a message ") ||
2512                     looking_at(buf, &i, "* just sent you a message:\n") ||
2513                     /* Whispers and kibitzes */
2514                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515                     looking_at(buf, &i, "* kibitzes: ") ||
2516                     /* Channel tells */
2517                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2518
2519                   if (tkind == 1 && strchr(star_match[0], ':')) {
2520                       /* Avoid "tells you:" spoofs in channels */
2521                      tkind = 3;
2522                   }
2523                   if (star_match[0][0] == NULLCHAR ||
2524                       strchr(star_match[0], ' ') ||
2525                       (tkind == 3 && strchr(star_match[1], ' '))) {
2526                     /* Reject bogus matches */
2527                     i = oldi;
2528                   } else {
2529                     if (appData.colorize) {
2530                       if (oldi > next_out) {
2531                         SendToPlayer(&buf[next_out], oldi - next_out);
2532                         next_out = oldi;
2533                       }
2534                       switch (tkind) {
2535                       case 1:
2536                         Colorize(ColorTell, FALSE);
2537                         curColor = ColorTell;
2538                         break;
2539                       case 2:
2540                         Colorize(ColorKibitz, FALSE);
2541                         curColor = ColorKibitz;
2542                         break;
2543                       case 3:
2544                         p = strrchr(star_match[1], '(');
2545                         if (p == NULL) {
2546                           p = star_match[1];
2547                         } else {
2548                           p++;
2549                         }
2550                         if (atoi(p) == 1) {
2551                           Colorize(ColorChannel1, FALSE);
2552                           curColor = ColorChannel1;
2553                         } else {
2554                           Colorize(ColorChannel, FALSE);
2555                           curColor = ColorChannel;
2556                         }
2557                         break;
2558                       case 5:
2559                         curColor = ColorNormal;
2560                         break;
2561                       }
2562                     }
2563                     if (started == STARTED_NONE && appData.autoComment &&
2564                         (gameMode == IcsObserving ||
2565                          gameMode == IcsPlayingWhite ||
2566                          gameMode == IcsPlayingBlack)) {
2567                       parse_pos = i - oldi;
2568                       memcpy(parse, &buf[oldi], parse_pos);
2569                       parse[parse_pos] = NULLCHAR;
2570                       started = STARTED_COMMENT;
2571                       savingComment = TRUE;
2572                     } else {
2573                       started = STARTED_CHATTER;
2574                       savingComment = FALSE;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                   }
2579                 }
2580
2581                 if (looking_at(buf, &i, "* s-shouts: ") ||
2582                     looking_at(buf, &i, "* c-shouts: ")) {
2583                     if (appData.colorize) {
2584                         if (oldi > next_out) {
2585                             SendToPlayer(&buf[next_out], oldi - next_out);
2586                             next_out = oldi;
2587                         }
2588                         Colorize(ColorSShout, FALSE);
2589                         curColor = ColorSShout;
2590                     }
2591                     loggedOn = TRUE;
2592                     started = STARTED_CHATTER;
2593                     continue;
2594                 }
2595
2596                 if (looking_at(buf, &i, "--->")) {
2597                     loggedOn = TRUE;
2598                     continue;
2599                 }
2600
2601                 if (looking_at(buf, &i, "* shouts: ") ||
2602                     looking_at(buf, &i, "--> ")) {
2603                     if (appData.colorize) {
2604                         if (oldi > next_out) {
2605                             SendToPlayer(&buf[next_out], oldi - next_out);
2606                             next_out = oldi;
2607                         }
2608                         Colorize(ColorShout, FALSE);
2609                         curColor = ColorShout;
2610                     }
2611                     loggedOn = TRUE;
2612                     started = STARTED_CHATTER;
2613                     continue;
2614                 }
2615
2616                 if (looking_at( buf, &i, "Challenge:")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorChallenge, FALSE);
2623                         curColor = ColorChallenge;
2624                     }
2625                     loggedOn = TRUE;
2626                     continue;
2627                 }
2628
2629                 if (looking_at(buf, &i, "* offers you") ||
2630                     looking_at(buf, &i, "* offers to be") ||
2631                     looking_at(buf, &i, "* would like to") ||
2632                     looking_at(buf, &i, "* requests to") ||
2633                     looking_at(buf, &i, "Your opponent offers") ||
2634                     looking_at(buf, &i, "Your opponent requests")) {
2635
2636                     if (appData.colorize) {
2637                         if (oldi > next_out) {
2638                             SendToPlayer(&buf[next_out], oldi - next_out);
2639                             next_out = oldi;
2640                         }
2641                         Colorize(ColorRequest, FALSE);
2642                         curColor = ColorRequest;
2643                     }
2644                     continue;
2645                 }
2646
2647                 if (looking_at(buf, &i, "* (*) seeking")) {
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorSeek, FALSE);
2654                         curColor = ColorSeek;
2655                     }
2656                     continue;
2657             }
2658
2659             if (looking_at(buf, &i, "\\   ")) {
2660                 if (prevColor != ColorNormal) {
2661                     if (oldi > next_out) {
2662                         SendToPlayer(&buf[next_out], oldi - next_out);
2663                         next_out = oldi;
2664                     }
2665                     Colorize(prevColor, TRUE);
2666                     curColor = prevColor;
2667                 }
2668                 if (savingComment) {
2669                     parse_pos = i - oldi;
2670                     memcpy(parse, &buf[oldi], parse_pos);
2671                     parse[parse_pos] = NULLCHAR;
2672                     started = STARTED_COMMENT;
2673                 } else {
2674                     started = STARTED_CHATTER;
2675                 }
2676                 continue;
2677             }
2678
2679             if (looking_at(buf, &i, "Black Strength :") ||
2680                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681                 looking_at(buf, &i, "<10>") ||
2682                 looking_at(buf, &i, "#@#")) {
2683                 /* Wrong board style */
2684                 loggedOn = TRUE;
2685                 SendToICS(ics_prefix);
2686                 SendToICS("set style 12\n");
2687                 SendToICS(ics_prefix);
2688                 SendToICS("refresh\n");
2689                 continue;
2690             }
2691             
2692             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2693                 ICSInitScript();
2694                 have_sent_ICS_logon = 1;
2695                 continue;
2696             }
2697               
2698             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2699                 (looking_at(buf, &i, "\n<12> ") ||
2700                  looking_at(buf, &i, "<12> "))) {
2701                 loggedOn = TRUE;
2702                 if (oldi > next_out) {
2703                     SendToPlayer(&buf[next_out], oldi - next_out);
2704                 }
2705                 next_out = i;
2706                 started = STARTED_BOARD;
2707                 parse_pos = 0;
2708                 continue;
2709             }
2710
2711             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712                 looking_at(buf, &i, "<b1> ")) {
2713                 if (oldi > next_out) {
2714                     SendToPlayer(&buf[next_out], oldi - next_out);
2715                 }
2716                 next_out = i;
2717                 started = STARTED_HOLDINGS;
2718                 parse_pos = 0;
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2723                 loggedOn = TRUE;
2724                 /* Header for a move list -- first line */
2725
2726                 switch (ics_getting_history) {
2727                   case H_FALSE:
2728                     switch (gameMode) {
2729                       case IcsIdle:
2730                       case BeginningOfGame:
2731                         /* User typed "moves" or "oldmoves" while we
2732                            were idle.  Pretend we asked for these
2733                            moves and soak them up so user can step
2734                            through them and/or save them.
2735                            */
2736                         Reset(FALSE, TRUE);
2737                         gameMode = IcsObserving;
2738                         ModeHighlight();
2739                         ics_gamenum = -1;
2740                         ics_getting_history = H_GOT_UNREQ_HEADER;
2741                         break;
2742                       case EditGame: /*?*/
2743                       case EditPosition: /*?*/
2744                         /* Should above feature work in these modes too? */
2745                         /* For now it doesn't */
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                       default:
2749                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2750                         break;
2751                     }
2752                     break;
2753                   case H_REQUESTED:
2754                     /* Is this the right one? */
2755                     if (gameInfo.white && gameInfo.black &&
2756                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2757                         strcmp(gameInfo.black, star_match[2]) == 0) {
2758                         /* All is well */
2759                         ics_getting_history = H_GOT_REQ_HEADER;
2760                     }
2761                     break;
2762                   case H_GOT_REQ_HEADER:
2763                   case H_GOT_UNREQ_HEADER:
2764                   case H_GOT_UNWANTED_HEADER:
2765                   case H_GETTING_MOVES:
2766                     /* Should not happen */
2767                     DisplayError(_("Error gathering move list: two headers"), 0);
2768                     ics_getting_history = H_FALSE;
2769                     break;
2770                 }
2771
2772                 /* Save player ratings into gameInfo if needed */
2773                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775                     (gameInfo.whiteRating == -1 ||
2776                      gameInfo.blackRating == -1)) {
2777
2778                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2779                     gameInfo.blackRating = string_to_rating(star_match[3]);
2780                     if (appData.debugMode)
2781                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2782                               gameInfo.whiteRating, gameInfo.blackRating);
2783                 }
2784                 continue;
2785             }
2786
2787             if (looking_at(buf, &i,
2788               "* * match, initial time: * minute*, increment: * second")) {
2789                 /* Header for a move list -- second line */
2790                 /* Initial board will follow if this is a wild game */
2791                 if (gameInfo.event != NULL) free(gameInfo.event);
2792                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793                 gameInfo.event = StrSave(str);
2794                 /* [HGM] we switched variant. Translate boards if needed. */
2795                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i, "Move  ")) {
2800                 /* Beginning of a move list */
2801                 switch (ics_getting_history) {
2802                   case H_FALSE:
2803                     /* Normally should not happen */
2804                     /* Maybe user hit reset while we were parsing */
2805                     break;
2806                   case H_REQUESTED:
2807                     /* Happens if we are ignoring a move list that is not
2808                      * the one we just requested.  Common if the user
2809                      * tries to observe two games without turning off
2810                      * getMoveList */
2811                     break;
2812                   case H_GETTING_MOVES:
2813                     /* Should not happen */
2814                     DisplayError(_("Error gathering move list: nested"), 0);
2815                     ics_getting_history = H_FALSE;
2816                     break;
2817                   case H_GOT_REQ_HEADER:
2818                     ics_getting_history = H_GETTING_MOVES;
2819                     started = STARTED_MOVES;
2820                     parse_pos = 0;
2821                     if (oldi > next_out) {
2822                         SendToPlayer(&buf[next_out], oldi - next_out);
2823                     }
2824                     break;
2825                   case H_GOT_UNREQ_HEADER:
2826                     ics_getting_history = H_GETTING_MOVES;
2827                     started = STARTED_MOVES_NOHIDE;
2828                     parse_pos = 0;
2829                     break;
2830                   case H_GOT_UNWANTED_HEADER:
2831                     ics_getting_history = H_FALSE;
2832                     break;
2833                 }
2834                 continue;
2835             }                           
2836             
2837             if (looking_at(buf, &i, "% ") ||
2838                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840                 savingComment = FALSE;
2841                 switch (started) {
2842                   case STARTED_MOVES:
2843                   case STARTED_MOVES_NOHIDE:
2844                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845                     parse[parse_pos + i - oldi] = NULLCHAR;
2846                     ParseGameHistory(parse);
2847 #if ZIPPY
2848                     if (appData.zippyPlay && first.initDone) {
2849                         FeedMovesToProgram(&first, forwardMostMove);
2850                         if (gameMode == IcsPlayingWhite) {
2851                             if (WhiteOnMove(forwardMostMove)) {
2852                                 if (first.sendTime) {
2853                                   if (first.useColors) {
2854                                     SendToProgram("black\n", &first); 
2855                                   }
2856                                   SendTimeRemaining(&first, TRUE);
2857                                 }
2858                                 if (first.useColors) {
2859                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2860                                 }
2861                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862                                 first.maybeThinking = TRUE;
2863                             } else {
2864                                 if (first.usePlayother) {
2865                                   if (first.sendTime) {
2866                                     SendTimeRemaining(&first, TRUE);
2867                                   }
2868                                   SendToProgram("playother\n", &first);
2869                                   firstMove = FALSE;
2870                                 } else {
2871                                   firstMove = TRUE;
2872                                 }
2873                             }
2874                         } else if (gameMode == IcsPlayingBlack) {
2875                             if (!WhiteOnMove(forwardMostMove)) {
2876                                 if (first.sendTime) {
2877                                   if (first.useColors) {
2878                                     SendToProgram("white\n", &first);
2879                                   }
2880                                   SendTimeRemaining(&first, FALSE);
2881                                 }
2882                                 if (first.useColors) {
2883                                   SendToProgram("black\n", &first);
2884                                 }
2885                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886                                 first.maybeThinking = TRUE;
2887                             } else {
2888                                 if (first.usePlayother) {
2889                                   if (first.sendTime) {
2890                                     SendTimeRemaining(&first, FALSE);
2891                                   }
2892                                   SendToProgram("playother\n", &first);
2893                                   firstMove = FALSE;
2894                                 } else {
2895                                   firstMove = TRUE;
2896                                 }
2897                             }
2898                         }                       
2899                     }
2900 #endif
2901                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2902                         /* Moves came from oldmoves or moves command
2903                            while we weren't doing anything else.
2904                            */
2905                         currentMove = forwardMostMove;
2906                         ClearHighlights();/*!!could figure this out*/
2907                         flipView = appData.flipView;
2908                         DrawPosition(TRUE, boards[currentMove]);
2909                         DisplayBothClocks();
2910                         sprintf(str, "%s vs. %s",
2911                                 gameInfo.white, gameInfo.black);
2912                         DisplayTitle(str);
2913                         gameMode = IcsIdle;
2914                     } else {
2915                         /* Moves were history of an active game */
2916                         if (gameInfo.resultDetails != NULL) {
2917                             free(gameInfo.resultDetails);
2918                             gameInfo.resultDetails = NULL;
2919                         }
2920                     }
2921                     HistorySet(parseList, backwardMostMove,
2922                                forwardMostMove, currentMove-1);
2923                     DisplayMove(currentMove - 1);
2924                     if (started == STARTED_MOVES) next_out = i;
2925                     started = STARTED_NONE;
2926                     ics_getting_history = H_FALSE;
2927                     break;
2928
2929                   case STARTED_OBSERVE:
2930                     started = STARTED_NONE;
2931                     SendToICS(ics_prefix);
2932                     SendToICS("refresh\n");
2933                     break;
2934
2935                   default:
2936                     break;
2937                 }
2938                 if(bookHit) { // [HGM] book: simulate book reply
2939                     static char bookMove[MSG_SIZ]; // a bit generous?
2940
2941                     programStats.nodes = programStats.depth = programStats.time = 
2942                     programStats.score = programStats.got_only_move = 0;
2943                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2944
2945                     strcpy(bookMove, "move ");
2946                     strcat(bookMove, bookHit);
2947                     HandleMachineMove(bookMove, &first);
2948                 }
2949                 continue;
2950             }
2951             
2952             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953                  started == STARTED_HOLDINGS ||
2954                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955                 /* Accumulate characters in move list or board */
2956                 parse[parse_pos++] = buf[i];
2957             }
2958             
2959             /* Start of game messages.  Mostly we detect start of game
2960                when the first board image arrives.  On some versions
2961                of the ICS, though, we need to do a "refresh" after starting
2962                to observe in order to get the current board right away. */
2963             if (looking_at(buf, &i, "Adding game * to observation list")) {
2964                 started = STARTED_OBSERVE;
2965                 continue;
2966             }
2967
2968             /* Handle auto-observe */
2969             if (appData.autoObserve &&
2970                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2972                 char *player;
2973                 /* Choose the player that was highlighted, if any. */
2974                 if (star_match[0][0] == '\033' ||
2975                     star_match[1][0] != '\033') {
2976                     player = star_match[0];
2977                 } else {
2978                     player = star_match[2];
2979                 }
2980                 sprintf(str, "%sobserve %s\n",
2981                         ics_prefix, StripHighlightAndTitle(player));
2982                 SendToICS(str);
2983
2984                 /* Save ratings from notify string */
2985                 strcpy(player1Name, star_match[0]);
2986                 player1Rating = string_to_rating(star_match[1]);
2987                 strcpy(player2Name, star_match[2]);
2988                 player2Rating = string_to_rating(star_match[3]);
2989
2990                 if (appData.debugMode)
2991                   fprintf(debugFP, 
2992                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2993                           player1Name, player1Rating,
2994                           player2Name, player2Rating);
2995
2996                 continue;
2997             }
2998
2999             /* Deal with automatic examine mode after a game,
3000                and with IcsObserving -> IcsExamining transition */
3001             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002                 looking_at(buf, &i, "has made you an examiner of game *")) {
3003
3004                 int gamenum = atoi(star_match[0]);
3005                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006                     gamenum == ics_gamenum) {
3007                     /* We were already playing or observing this game;
3008                        no need to refetch history */
3009                     gameMode = IcsExamining;
3010                     if (pausing) {
3011                         pauseExamForwardMostMove = forwardMostMove;
3012                     } else if (currentMove < forwardMostMove) {
3013                         ForwardInner(forwardMostMove);
3014                     }
3015                 } else {
3016                     /* I don't think this case really can happen */
3017                     SendToICS(ics_prefix);
3018                     SendToICS("refresh\n");
3019                 }
3020                 continue;
3021             }    
3022             
3023             /* Error messages */
3024 //          if (ics_user_moved) {
3025             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026                 if (looking_at(buf, &i, "Illegal move") ||
3027                     looking_at(buf, &i, "Not a legal move") ||
3028                     looking_at(buf, &i, "Your king is in check") ||
3029                     looking_at(buf, &i, "It isn't your turn") ||
3030                     looking_at(buf, &i, "It is not your move")) {
3031                     /* Illegal move */
3032                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033                         currentMove = --forwardMostMove;
3034                         DisplayMove(currentMove - 1); /* before DMError */
3035                         DrawPosition(FALSE, boards[currentMove]);
3036                         SwitchClocks();
3037                         DisplayBothClocks();
3038                     }
3039                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3040                     ics_user_moved = 0;
3041                     continue;
3042                 }
3043             }
3044
3045             if (looking_at(buf, &i, "still have time") ||
3046                 looking_at(buf, &i, "not out of time") ||
3047                 looking_at(buf, &i, "either player is out of time") ||
3048                 looking_at(buf, &i, "has timeseal; checking")) {
3049                 /* We must have called his flag a little too soon */
3050                 whiteFlag = blackFlag = FALSE;
3051                 continue;
3052             }
3053
3054             if (looking_at(buf, &i, "added * seconds to") ||
3055                 looking_at(buf, &i, "seconds were added to")) {
3056                 /* Update the clocks */
3057                 SendToICS(ics_prefix);
3058                 SendToICS("refresh\n");
3059                 continue;
3060             }
3061
3062             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063                 ics_clock_paused = TRUE;
3064                 StopClocks();
3065                 continue;
3066             }
3067
3068             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069                 ics_clock_paused = FALSE;
3070                 StartClocks();
3071                 continue;
3072             }
3073
3074             /* Grab player ratings from the Creating: message.
3075                Note we have to check for the special case when
3076                the ICS inserts things like [white] or [black]. */
3077             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3079                 /* star_matches:
3080                    0    player 1 name (not necessarily white)
3081                    1    player 1 rating
3082                    2    empty, white, or black (IGNORED)
3083                    3    player 2 name (not necessarily black)
3084                    4    player 2 rating
3085                    
3086                    The names/ratings are sorted out when the game
3087                    actually starts (below).
3088                 */
3089                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090                 player1Rating = string_to_rating(star_match[1]);
3091                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092                 player2Rating = string_to_rating(star_match[4]);
3093
3094                 if (appData.debugMode)
3095                   fprintf(debugFP, 
3096                           "Ratings from 'Creating:' %s %d, %s %d\n",
3097                           player1Name, player1Rating,
3098                           player2Name, player2Rating);
3099
3100                 continue;
3101             }
3102             
3103             /* Improved generic start/end-of-game messages */
3104             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106                 /* If tkind == 0: */
3107                 /* star_match[0] is the game number */
3108                 /*           [1] is the white player's name */
3109                 /*           [2] is the black player's name */
3110                 /* For end-of-game: */
3111                 /*           [3] is the reason for the game end */
3112                 /*           [4] is a PGN end game-token, preceded by " " */
3113                 /* For start-of-game: */
3114                 /*           [3] begins with "Creating" or "Continuing" */
3115                 /*           [4] is " *" or empty (don't care). */
3116                 int gamenum = atoi(star_match[0]);
3117                 char *whitename, *blackname, *why, *endtoken;
3118                 ChessMove endtype = (ChessMove) 0;
3119
3120                 if (tkind == 0) {
3121                   whitename = star_match[1];
3122                   blackname = star_match[2];
3123                   why = star_match[3];
3124                   endtoken = star_match[4];
3125                 } else {
3126                   whitename = star_match[1];
3127                   blackname = star_match[3];
3128                   why = star_match[5];
3129                   endtoken = star_match[6];
3130                 }
3131
3132                 /* Game start messages */
3133                 if (strncmp(why, "Creating ", 9) == 0 ||
3134                     strncmp(why, "Continuing ", 11) == 0) {
3135                     gs_gamenum = gamenum;
3136                     strcpy(gs_kind, strchr(why, ' ') + 1);
3137                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3138 #if ZIPPY
3139                     if (appData.zippyPlay) {
3140                         ZippyGameStart(whitename, blackname);
3141                     }
3142 #endif /*ZIPPY*/
3143                     continue;
3144                 }
3145
3146                 /* Game end messages */
3147                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3148                     ics_gamenum != gamenum) {
3149                     continue;
3150                 }
3151                 while (endtoken[0] == ' ') endtoken++;
3152                 switch (endtoken[0]) {
3153                   case '*':
3154                   default:
3155                     endtype = GameUnfinished;
3156                     break;
3157                   case '0':
3158                     endtype = BlackWins;
3159                     break;
3160                   case '1':
3161                     if (endtoken[1] == '/')
3162                       endtype = GameIsDrawn;
3163                     else
3164                       endtype = WhiteWins;
3165                     break;
3166                 }
3167                 GameEnds(endtype, why, GE_ICS);
3168 #if ZIPPY
3169                 if (appData.zippyPlay && first.initDone) {
3170                     ZippyGameEnd(endtype, why);
3171                     if (first.pr == NULL) {
3172                       /* Start the next process early so that we'll
3173                          be ready for the next challenge */
3174                       StartChessProgram(&first);
3175                     }
3176                     /* Send "new" early, in case this command takes
3177                        a long time to finish, so that we'll be ready
3178                        for the next challenge. */
3179                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3180                     Reset(TRUE, TRUE);
3181                 }
3182 #endif /*ZIPPY*/
3183                 continue;
3184             }
3185
3186             if (looking_at(buf, &i, "Removing game * from observation") ||
3187                 looking_at(buf, &i, "no longer observing game *") ||
3188                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3189                 if (gameMode == IcsObserving &&
3190                     atoi(star_match[0]) == ics_gamenum)
3191                   {
3192                       /* icsEngineAnalyze */
3193                       if (appData.icsEngineAnalyze) {
3194                             ExitAnalyzeMode();
3195                             ModeHighlight();
3196                       }
3197                       StopClocks();
3198                       gameMode = IcsIdle;
3199                       ics_gamenum = -1;
3200                       ics_user_moved = FALSE;
3201                   }
3202                 continue;
3203             }
3204
3205             if (looking_at(buf, &i, "no longer examining game *")) {
3206                 if (gameMode == IcsExamining &&
3207                     atoi(star_match[0]) == ics_gamenum)
3208                   {
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             /* Advance leftover_start past any newlines we find,
3217                so only partial lines can get reparsed */
3218             if (looking_at(buf, &i, "\n")) {
3219                 prevColor = curColor;
3220                 if (curColor != ColorNormal) {
3221                     if (oldi > next_out) {
3222                         SendToPlayer(&buf[next_out], oldi - next_out);
3223                         next_out = oldi;
3224                     }
3225                     Colorize(ColorNormal, FALSE);
3226                     curColor = ColorNormal;
3227                 }
3228                 if (started == STARTED_BOARD) {
3229                     started = STARTED_NONE;
3230                     parse[parse_pos] = NULLCHAR;
3231                     ParseBoard12(parse);
3232                     ics_user_moved = 0;
3233
3234                     /* Send premove here */
3235                     if (appData.premove) {
3236                       char str[MSG_SIZ];
3237                       if (currentMove == 0 &&
3238                           gameMode == IcsPlayingWhite &&
3239                           appData.premoveWhite) {
3240                         sprintf(str, "%s\n", appData.premoveWhiteText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (currentMove == 1 &&
3245                                  gameMode == IcsPlayingBlack &&
3246                                  appData.premoveBlack) {
3247                         sprintf(str, "%s\n", appData.premoveBlackText);
3248                         if (appData.debugMode)
3249                           fprintf(debugFP, "Sending premove:\n");
3250                         SendToICS(str);
3251                       } else if (gotPremove) {
3252                         gotPremove = 0;
3253                         ClearPremoveHighlights();
3254                         if (appData.debugMode)
3255                           fprintf(debugFP, "Sending premove:\n");
3256                           UserMoveEvent(premoveFromX, premoveFromY, 
3257                                         premoveToX, premoveToY, 
3258                                         premovePromoChar);
3259                       }
3260                     }
3261
3262                     /* Usually suppress following prompt */
3263                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3264                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3265                         if (looking_at(buf, &i, "*% ")) {
3266                             savingComment = FALSE;
3267                         }
3268                     }
3269                     next_out = i;
3270                 } else if (started == STARTED_HOLDINGS) {
3271                     int gamenum;
3272                     char new_piece[MSG_SIZ];
3273                     started = STARTED_NONE;
3274                     parse[parse_pos] = NULLCHAR;
3275                     if (appData.debugMode)
3276                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3277                                                         parse, currentMove);
3278                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3279                         gamenum == ics_gamenum) {
3280                         if (gameInfo.variant == VariantNormal) {
3281                           /* [HGM] We seem to switch variant during a game!
3282                            * Presumably no holdings were displayed, so we have
3283                            * to move the position two files to the right to
3284                            * create room for them!
3285                            */
3286                           VariantClass newVariant;
3287                           switch(gameInfo.boardWidth) { // base guess on board width
3288                                 case 9:  newVariant = VariantShogi; break;
3289                                 case 10: newVariant = VariantGreat; break;
3290                                 default: newVariant = VariantCrazyhouse; break;
3291                           }
3292                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3293                           /* Get a move list just to see the header, which
3294                              will tell us whether this is really bug or zh */
3295                           if (ics_getting_history == H_FALSE) {
3296                             ics_getting_history = H_REQUESTED;
3297                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3298                             SendToICS(str);
3299                           }
3300                         }
3301                         new_piece[0] = NULLCHAR;
3302                         sscanf(parse, "game %d white [%s black [%s <- %s",
3303                                &gamenum, white_holding, black_holding,
3304                                new_piece);
3305                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3306                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3307                         /* [HGM] copy holdings to board holdings area */
3308                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3309                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3310                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3311 #if ZIPPY
3312                         if (appData.zippyPlay && first.initDone) {
3313                             ZippyHoldings(white_holding, black_holding,
3314                                           new_piece);
3315                         }
3316 #endif /*ZIPPY*/
3317                         if (tinyLayout || smallLayout) {
3318                             char wh[16], bh[16];
3319                             PackHolding(wh, white_holding);
3320                             PackHolding(bh, black_holding);
3321                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3322                                     gameInfo.white, gameInfo.black);
3323                         } else {
3324                             sprintf(str, "%s [%s] vs. %s [%s]",
3325                                     gameInfo.white, white_holding,
3326                                     gameInfo.black, black_holding);
3327                         }
3328
3329                         DrawPosition(FALSE, boards[currentMove]);
3330                         DisplayTitle(str);
3331                     }
3332                     /* Suppress following prompt */
3333                     if (looking_at(buf, &i, "*% ")) {
3334                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3335                         savingComment = FALSE;
3336                     }
3337                     next_out = i;
3338                 }
3339                 continue;
3340             }
3341
3342             i++;                /* skip unparsed character and loop back */
3343         }
3344         
3345         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3346             started != STARTED_HOLDINGS && i > next_out) {
3347             SendToPlayer(&buf[next_out], i - next_out);
3348             next_out = i;
3349         }
3350         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3351         
3352         leftover_len = buf_len - leftover_start;
3353         /* if buffer ends with something we couldn't parse,
3354            reparse it after appending the next read */
3355         
3356     } else if (count == 0) {
3357         RemoveInputSource(isr);
3358         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3359     } else {
3360         DisplayFatalError(_("Error reading from ICS"), error, 1);
3361     }
3362 }
3363
3364
3365 /* Board style 12 looks like this:
3366    
3367    <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
3368    
3369  * The "<12> " is stripped before it gets to this routine.  The two
3370  * trailing 0's (flip state and clock ticking) are later addition, and
3371  * some chess servers may not have them, or may have only the first.
3372  * Additional trailing fields may be added in the future.  
3373  */
3374
3375 #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"
3376
3377 #define RELATION_OBSERVING_PLAYED    0
3378 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3379 #define RELATION_PLAYING_MYMOVE      1
3380 #define RELATION_PLAYING_NOTMYMOVE  -1
3381 #define RELATION_EXAMINING           2
3382 #define RELATION_ISOLATED_BOARD     -3
3383 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3384
3385 void
3386 ParseBoard12(string)
3387      char *string;
3388
3389     GameMode newGameMode;
3390     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3391     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3392     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3393     char to_play, board_chars[200];
3394     char move_str[500], str[500], elapsed_time[500];
3395     char black[32], white[32];
3396     Board board;
3397     int prevMove = currentMove;
3398     int ticking = 2;
3399     ChessMove moveType;
3400     int fromX, fromY, toX, toY;
3401     char promoChar;
3402     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3403     char *bookHit = NULL; // [HGM] book
3404     Boolean weird = FALSE, reqFlag = FALSE;
3405
3406     fromX = fromY = toX = toY = -1;
3407     
3408     newGame = FALSE;
3409
3410     if (appData.debugMode)
3411       fprintf(debugFP, _("Parsing board: %s\n"), string);
3412
3413     move_str[0] = NULLCHAR;
3414     elapsed_time[0] = NULLCHAR;
3415     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3416         int  i = 0, j;
3417         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3418             if(string[i] == ' ') { ranks++; files = 0; }
3419             else files++;
3420             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3421             i++;
3422         }
3423         for(j = 0; j <i; j++) board_chars[j] = string[j];
3424         board_chars[i] = '\0';
3425         string += i + 1;
3426     }
3427     n = sscanf(string, PATTERN, &to_play, &double_push,
3428                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3429                &gamenum, white, black, &relation, &basetime, &increment,
3430                &white_stren, &black_stren, &white_time, &black_time,
3431                &moveNum, str, elapsed_time, move_str, &ics_flip,
3432                &ticking);
3433
3434     if (n < 21) {
3435         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3436         DisplayError(str, 0);
3437         return;
3438     }
3439
3440     /* Convert the move number to internal form */
3441     moveNum = (moveNum - 1) * 2;
3442     if (to_play == 'B') moveNum++;
3443     if (moveNum >= MAX_MOVES) {
3444       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3445                         0, 1);
3446       return;
3447     }
3448     
3449     switch (relation) {
3450       case RELATION_OBSERVING_PLAYED:
3451       case RELATION_OBSERVING_STATIC:
3452         if (gamenum == -1) {
3453             /* Old ICC buglet */
3454             relation = RELATION_OBSERVING_STATIC;
3455         }
3456         newGameMode = IcsObserving;
3457         break;
3458       case RELATION_PLAYING_MYMOVE:
3459       case RELATION_PLAYING_NOTMYMOVE:
3460         newGameMode =
3461           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3462             IcsPlayingWhite : IcsPlayingBlack;
3463         break;
3464       case RELATION_EXAMINING:
3465         newGameMode = IcsExamining;
3466         break;
3467       case RELATION_ISOLATED_BOARD:
3468       default:
3469         /* Just display this board.  If user was doing something else,
3470            we will forget about it until the next board comes. */ 
3471         newGameMode = IcsIdle;
3472         break;
3473       case RELATION_STARTING_POSITION:
3474         newGameMode = gameMode;
3475         break;
3476     }
3477     
3478     /* Modify behavior for initial board display on move listing
3479        of wild games.
3480        */
3481     switch (ics_getting_history) {
3482       case H_FALSE:
3483       case H_REQUESTED:
3484         break;
3485       case H_GOT_REQ_HEADER:
3486       case H_GOT_UNREQ_HEADER:
3487         /* This is the initial position of the current game */
3488         gamenum = ics_gamenum;
3489         moveNum = 0;            /* old ICS bug workaround */
3490         if (to_play == 'B') {
3491           startedFromSetupPosition = TRUE;
3492           blackPlaysFirst = TRUE;
3493           moveNum = 1;
3494           if (forwardMostMove == 0) forwardMostMove = 1;
3495           if (backwardMostMove == 0) backwardMostMove = 1;
3496           if (currentMove == 0) currentMove = 1;
3497         }
3498         newGameMode = gameMode;
3499         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3500         break;
3501       case H_GOT_UNWANTED_HEADER:
3502         /* This is an initial board that we don't want */
3503         return;
3504       case H_GETTING_MOVES:
3505         /* Should not happen */
3506         DisplayError(_("Error gathering move list: extra board"), 0);
3507         ics_getting_history = H_FALSE;
3508         return;
3509     }
3510
3511    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3512                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3513      /* [HGM] We seem to have switched variant unexpectedly
3514       * Try to guess new variant from board size
3515       */
3516           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3517           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3518           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3519           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3520           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3521           if(!weird) newVariant = VariantNormal;
3522           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3523           /* Get a move list just to see the header, which
3524              will tell us whether this is really bug or zh */
3525           if (ics_getting_history == H_FALSE) {
3526             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3527             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3528             SendToICS(str);
3529           }
3530     }
3531     
3532     /* Take action if this is the first board of a new game, or of a
3533        different game than is currently being displayed.  */
3534     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3535         relation == RELATION_ISOLATED_BOARD) {
3536         
3537         /* Forget the old game and get the history (if any) of the new one */
3538         if (gameMode != BeginningOfGame) {
3539           Reset(TRUE, TRUE);
3540         }
3541         newGame = TRUE;
3542         if (appData.autoRaiseBoard) BoardToTop();
3543         prevMove = -3;
3544         if (gamenum == -1) {
3545             newGameMode = IcsIdle;
3546         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3547                    appData.getMoveList && !reqFlag) {
3548             /* Need to get game history */
3549             ics_getting_history = H_REQUESTED;
3550             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3551             SendToICS(str);
3552         }
3553         
3554         /* Initially flip the board to have black on the bottom if playing
3555            black or if the ICS flip flag is set, but let the user change
3556            it with the Flip View button. */
3557         flipView = appData.autoFlipView ? 
3558           (newGameMode == IcsPlayingBlack) || ics_flip :
3559           appData.flipView;
3560         
3561         /* Done with values from previous mode; copy in new ones */
3562         gameMode = newGameMode;
3563         ModeHighlight();
3564         ics_gamenum = gamenum;
3565         if (gamenum == gs_gamenum) {
3566             int klen = strlen(gs_kind);
3567             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3568             sprintf(str, "ICS %s", gs_kind);
3569             gameInfo.event = StrSave(str);
3570         } else {
3571             gameInfo.event = StrSave("ICS game");
3572         }
3573         gameInfo.site = StrSave(appData.icsHost);
3574         gameInfo.date = PGNDate();
3575         gameInfo.round = StrSave("-");
3576         gameInfo.white = StrSave(white);
3577         gameInfo.black = StrSave(black);
3578         timeControl = basetime * 60 * 1000;
3579         timeControl_2 = 0;
3580         timeIncrement = increment * 1000;
3581         movesPerSession = 0;
3582         gameInfo.timeControl = TimeControlTagValue();
3583         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3584   if (appData.debugMode) {
3585     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3586     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3587     setbuf(debugFP, NULL);
3588   }
3589
3590         gameInfo.outOfBook = NULL;
3591         
3592         /* Do we have the ratings? */
3593         if (strcmp(player1Name, white) == 0 &&
3594             strcmp(player2Name, black) == 0) {
3595             if (appData.debugMode)
3596               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3597                       player1Rating, player2Rating);
3598             gameInfo.whiteRating = player1Rating;
3599             gameInfo.blackRating = player2Rating;
3600         } else if (strcmp(player2Name, white) == 0 &&
3601                    strcmp(player1Name, black) == 0) {
3602             if (appData.debugMode)
3603               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3604                       player2Rating, player1Rating);
3605             gameInfo.whiteRating = player2Rating;
3606             gameInfo.blackRating = player1Rating;
3607         }
3608         player1Name[0] = player2Name[0] = NULLCHAR;
3609
3610         /* Silence shouts if requested */
3611         if (appData.quietPlay &&
3612             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3613             SendToICS(ics_prefix);
3614             SendToICS("set shout 0\n");
3615         }
3616     }
3617     
3618     /* Deal with midgame name changes */
3619     if (!newGame) {
3620         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3621             if (gameInfo.white) free(gameInfo.white);
3622             gameInfo.white = StrSave(white);
3623         }
3624         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3625             if (gameInfo.black) free(gameInfo.black);
3626             gameInfo.black = StrSave(black);
3627         }
3628     }
3629     
3630     /* Throw away game result if anything actually changes in examine mode */
3631     if (gameMode == IcsExamining && !newGame) {
3632         gameInfo.result = GameUnfinished;
3633         if (gameInfo.resultDetails != NULL) {
3634             free(gameInfo.resultDetails);
3635             gameInfo.resultDetails = NULL;
3636         }
3637     }
3638     
3639     /* In pausing && IcsExamining mode, we ignore boards coming
3640        in if they are in a different variation than we are. */
3641     if (pauseExamInvalid) return;
3642     if (pausing && gameMode == IcsExamining) {
3643         if (moveNum <= pauseExamForwardMostMove) {
3644             pauseExamInvalid = TRUE;
3645             forwardMostMove = pauseExamForwardMostMove;
3646             return;
3647         }
3648     }
3649     
3650   if (appData.debugMode) {
3651     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3652   }
3653     /* Parse the board */
3654     for (k = 0; k < ranks; k++) {
3655       for (j = 0; j < files; j++)
3656         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3657       if(gameInfo.holdingsWidth > 1) {
3658            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3659            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3660       }
3661     }
3662     CopyBoard(boards[moveNum], board);
3663     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3664     if (moveNum == 0) {
3665         startedFromSetupPosition =
3666           !CompareBoards(board, initialPosition);
3667         if(startedFromSetupPosition)
3668             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3669     }
3670
3671     /* [HGM] Set castling rights. Take the outermost Rooks,
3672        to make it also work for FRC opening positions. Note that board12
3673        is really defective for later FRC positions, as it has no way to
3674        indicate which Rook can castle if they are on the same side of King.
3675        For the initial position we grant rights to the outermost Rooks,
3676        and remember thos rights, and we then copy them on positions
3677        later in an FRC game. This means WB might not recognize castlings with
3678        Rooks that have moved back to their original position as illegal,
3679        but in ICS mode that is not its job anyway.
3680     */
3681     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3682     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3683
3684         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685             if(board[0][i] == WhiteRook) j = i;
3686         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688             if(board[0][i] == WhiteRook) j = i;
3689         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3690         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3693         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3694             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3695         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3696
3697         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3700         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3701             if(board[BOARD_HEIGHT-1][k] == bKing)
3702                 initialRights[5] = castlingRights[moveNum][5] = k;
3703     } else { int r;
3704         r = castlingRights[moveNum][0] = initialRights[0];
3705         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3706         r = castlingRights[moveNum][1] = initialRights[1];
3707         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3708         r = castlingRights[moveNum][3] = initialRights[3];
3709         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3710         r = castlingRights[moveNum][4] = initialRights[4];
3711         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3712         /* wildcastle kludge: always assume King has rights */
3713         r = castlingRights[moveNum][2] = initialRights[2];
3714         r = castlingRights[moveNum][5] = initialRights[5];
3715     }
3716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3717     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3718
3719     
3720     if (ics_getting_history == H_GOT_REQ_HEADER ||
3721         ics_getting_history == H_GOT_UNREQ_HEADER) {
3722         /* This was an initial position from a move list, not
3723            the current position */
3724         return;
3725     }
3726     
3727     /* Update currentMove and known move number limits */
3728     newMove = newGame || moveNum > forwardMostMove;
3729
3730     if (newGame) {
3731         forwardMostMove = backwardMostMove = currentMove = moveNum;
3732         if (gameMode == IcsExamining && moveNum == 0) {
3733           /* Workaround for ICS limitation: we are not told the wild
3734              type when starting to examine a game.  But if we ask for
3735              the move list, the move list header will tell us */
3736             ics_getting_history = H_REQUESTED;
3737             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3738             SendToICS(str);
3739         }
3740     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3741                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3742 #if ZIPPY
3743         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3744         /* [HGM] applied this also to an engine that is silently watching        */
3745         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3746             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3747             gameInfo.variant == currentlyInitializedVariant) {
3748           takeback = forwardMostMove - moveNum;
3749           for (i = 0; i < takeback; i++) {
3750             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3751             SendToProgram("undo\n", &first);
3752           }
3753         }
3754 #endif
3755
3756         forwardMostMove = moveNum;
3757         if (!pausing || currentMove > forwardMostMove)
3758           currentMove = forwardMostMove;
3759     } else {
3760         /* New part of history that is not contiguous with old part */ 
3761         if (pausing && gameMode == IcsExamining) {
3762             pauseExamInvalid = TRUE;
3763             forwardMostMove = pauseExamForwardMostMove;
3764             return;
3765         }
3766         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3767 #if ZIPPY
3768             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3769                 // [HGM] when we will receive the move list we now request, it will be
3770                 // fed to the engine from the first move on. So if the engine is not
3771                 // in the initial position now, bring it there.
3772                 InitChessProgram(&first, 0);
3773             }
3774 #endif
3775             ics_getting_history = H_REQUESTED;
3776             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3777             SendToICS(str);
3778         }
3779         forwardMostMove = backwardMostMove = currentMove = moveNum;
3780     }
3781     
3782     /* Update the clocks */
3783     if (strchr(elapsed_time, '.')) {
3784       /* Time is in ms */
3785       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3786       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3787     } else {
3788       /* Time is in seconds */
3789       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3790       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3791     }
3792       
3793
3794 #if ZIPPY
3795     if (appData.zippyPlay && newGame &&
3796         gameMode != IcsObserving && gameMode != IcsIdle &&
3797         gameMode != IcsExamining)
3798       ZippyFirstBoard(moveNum, basetime, increment);
3799 #endif
3800     
3801     /* Put the move on the move list, first converting
3802        to canonical algebraic form. */
3803     if (moveNum > 0) {
3804   if (appData.debugMode) {
3805     if (appData.debugMode) { int f = forwardMostMove;
3806         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3807                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3808     }
3809     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3810     fprintf(debugFP, "moveNum = %d\n", moveNum);
3811     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3812     setbuf(debugFP, NULL);
3813   }
3814         if (moveNum <= backwardMostMove) {
3815             /* We don't know what the board looked like before
3816                this move.  Punt. */
3817             strcpy(parseList[moveNum - 1], move_str);
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             moveList[moveNum - 1][0] = NULLCHAR;
3821         } else if (strcmp(move_str, "none") == 0) {
3822             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3823             /* Again, we don't know what the board looked like;
3824                this is really the start of the game. */
3825             parseList[moveNum - 1][0] = NULLCHAR;
3826             moveList[moveNum - 1][0] = NULLCHAR;
3827             backwardMostMove = moveNum;
3828             startedFromSetupPosition = TRUE;
3829             fromX = fromY = toX = toY = -1;
3830         } else {
3831           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3832           //                 So we parse the long-algebraic move string in stead of the SAN move
3833           int valid; char buf[MSG_SIZ], *prom;
3834
3835           // str looks something like "Q/a1-a2"; kill the slash
3836           if(str[1] == '/') 
3837                 sprintf(buf, "%c%s", str[0], str+2);
3838           else  strcpy(buf, str); // might be castling
3839           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3840                 strcat(buf, prom); // long move lacks promo specification!
3841           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3842                 if(appData.debugMode) 
3843                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3844                 strcpy(move_str, buf);
3845           }
3846           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar)
3848                || ParseOneMove(buf, moveNum - 1, &moveType,
3849                                 &fromX, &fromY, &toX, &toY, &promoChar);
3850           // end of long SAN patch
3851           if (valid) {
3852             (void) CoordsToAlgebraic(boards[moveNum - 1],
3853                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3854                                      fromY, fromX, toY, toX, promoChar,
3855                                      parseList[moveNum-1]);
3856             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3857                              castlingRights[moveNum]) ) {
3858               case MT_NONE:
3859               case MT_STALEMATE:
3860               default:
3861                 break;
3862               case MT_CHECK:
3863                 if(gameInfo.variant != VariantShogi)
3864                     strcat(parseList[moveNum - 1], "+");
3865                 break;
3866               case MT_CHECKMATE:
3867               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3868                 strcat(parseList[moveNum - 1], "#");
3869                 break;
3870             }
3871             strcat(parseList[moveNum - 1], " ");
3872             strcat(parseList[moveNum - 1], elapsed_time);
3873             /* currentMoveString is set as a side-effect of ParseOneMove */
3874             strcpy(moveList[moveNum - 1], currentMoveString);
3875             strcat(moveList[moveNum - 1], "\n");
3876           } else {
3877             /* Move from ICS was illegal!?  Punt. */
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3880     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3881   }
3882             strcpy(parseList[moveNum - 1], move_str);
3883             strcat(parseList[moveNum - 1], " ");
3884             strcat(parseList[moveNum - 1], elapsed_time);
3885             moveList[moveNum - 1][0] = NULLCHAR;
3886             fromX = fromY = toX = toY = -1;
3887           }
3888         }
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3891     setbuf(debugFP, NULL);
3892   }
3893
3894 #if ZIPPY
3895         /* Send move to chess program (BEFORE animating it). */
3896         if (appData.zippyPlay && !newGame && newMove && 
3897            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3898
3899             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3900                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3901                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3902                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3903                             move_str);
3904                     DisplayError(str, 0);
3905                 } else {
3906                     if (first.sendTime) {
3907                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3908                     }
3909                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3910                     if (firstMove && !bookHit) {
3911                         firstMove = FALSE;
3912                         if (first.useColors) {
3913                           SendToProgram(gameMode == IcsPlayingWhite ?
3914                                         "white\ngo\n" :
3915                                         "black\ngo\n", &first);
3916                         } else {
3917                           SendToProgram("go\n", &first);
3918                         }
3919                         first.maybeThinking = TRUE;
3920                     }
3921                 }
3922             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3923               if (moveList[moveNum - 1][0] == NULLCHAR) {
3924                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3925                 DisplayError(str, 0);
3926               } else {
3927                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3928                 SendMoveToProgram(moveNum - 1, &first);
3929               }
3930             }
3931         }
3932 #endif
3933     }
3934
3935     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3936         /* If move comes from a remote source, animate it.  If it
3937            isn't remote, it will have already been animated. */
3938         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3939             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3940         }
3941         if (!pausing && appData.highlightLastMove) {
3942             SetHighlights(fromX, fromY, toX, toY);
3943         }
3944     }
3945     
3946     /* Start the clocks */
3947     whiteFlag = blackFlag = FALSE;
3948     appData.clockMode = !(basetime == 0 && increment == 0);
3949     if (ticking == 0) {
3950       ics_clock_paused = TRUE;
3951       StopClocks();
3952     } else if (ticking == 1) {
3953       ics_clock_paused = FALSE;
3954     }
3955     if (gameMode == IcsIdle ||
3956         relation == RELATION_OBSERVING_STATIC ||
3957         relation == RELATION_EXAMINING ||
3958         ics_clock_paused)
3959       DisplayBothClocks();
3960     else
3961       StartClocks();
3962     
3963     /* Display opponents and material strengths */
3964     if (gameInfo.variant != VariantBughouse &&
3965         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3966         if (tinyLayout || smallLayout) {
3967             if(gameInfo.variant == VariantNormal)
3968                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3969                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3970                     basetime, increment);
3971             else
3972                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3974                     basetime, increment, (int) gameInfo.variant);
3975         } else {
3976             if(gameInfo.variant == VariantNormal)
3977                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3978                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3979                     basetime, increment);
3980             else
3981                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3982                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3983                     basetime, increment, VariantName(gameInfo.variant));
3984         }
3985         DisplayTitle(str);
3986   if (appData.debugMode) {
3987     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3988   }
3989     }
3990
3991    
3992     /* Display the board */
3993     if (!pausing && !appData.noGUI) {
3994       
3995       if (appData.premove)
3996           if (!gotPremove || 
3997              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3998              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3999               ClearPremoveHighlights();
4000
4001       DrawPosition(FALSE, boards[currentMove]);
4002       DisplayMove(moveNum - 1);
4003       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4004             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4005               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4006         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4007       }
4008     }
4009
4010     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4011 #if ZIPPY
4012     if(bookHit) { // [HGM] book: simulate book reply
4013         static char bookMove[MSG_SIZ]; // a bit generous?
4014
4015         programStats.nodes = programStats.depth = programStats.time = 
4016         programStats.score = programStats.got_only_move = 0;
4017         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4018
4019         strcpy(bookMove, "move ");
4020         strcat(bookMove, bookHit);
4021         HandleMachineMove(bookMove, &first);
4022     }
4023 #endif
4024 }
4025
4026 void
4027 GetMoveListEvent()
4028 {
4029     char buf[MSG_SIZ];
4030     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4031         ics_getting_history = H_REQUESTED;
4032         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4033         SendToICS(buf);
4034     }
4035 }
4036
4037 void
4038 AnalysisPeriodicEvent(force)
4039      int force;
4040 {
4041     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4042          && !force) || !appData.periodicUpdates)
4043       return;
4044
4045     /* Send . command to Crafty to collect stats */
4046     SendToProgram(".\n", &first);
4047
4048     /* Don't send another until we get a response (this makes
4049        us stop sending to old Crafty's which don't understand
4050        the "." command (sending illegal cmds resets node count & time,
4051        which looks bad)) */
4052     programStats.ok_to_send = 0;
4053 }
4054
4055 void ics_update_width(new_width)
4056         int new_width;
4057 {
4058         ics_printf("set width %d\n", new_width);
4059 }
4060
4061 void
4062 SendMoveToProgram(moveNum, cps)
4063      int moveNum;
4064      ChessProgramState *cps;
4065 {
4066     char buf[MSG_SIZ];
4067
4068     if (cps->useUsermove) {
4069       SendToProgram("usermove ", cps);
4070     }
4071     if (cps->useSAN) {
4072       char *space;
4073       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4074         int len = space - parseList[moveNum];
4075         memcpy(buf, parseList[moveNum], len);
4076         buf[len++] = '\n';
4077         buf[len] = NULLCHAR;
4078       } else {
4079         sprintf(buf, "%s\n", parseList[moveNum]);
4080       }
4081       SendToProgram(buf, cps);
4082     } else {
4083       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4084         AlphaRank(moveList[moveNum], 4);
4085         SendToProgram(moveList[moveNum], cps);
4086         AlphaRank(moveList[moveNum], 4); // and back
4087       } else
4088       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4089        * the engine. It would be nice to have a better way to identify castle 
4090        * moves here. */
4091       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4092                                                                          && cps->useOOCastle) {
4093         int fromX = moveList[moveNum][0] - AAA; 
4094         int fromY = moveList[moveNum][1] - ONE;
4095         int toX = moveList[moveNum][2] - AAA; 
4096         int toY = moveList[moveNum][3] - ONE;
4097         if((boards[moveNum][fromY][fromX] == WhiteKing 
4098             && boards[moveNum][toY][toX] == WhiteRook)
4099            || (boards[moveNum][fromY][fromX] == BlackKing 
4100                && boards[moveNum][toY][toX] == BlackRook)) {
4101           if(toX > fromX) SendToProgram("O-O\n", cps);
4102           else SendToProgram("O-O-O\n", cps);
4103         }
4104         else SendToProgram(moveList[moveNum], cps);
4105       }
4106       else SendToProgram(moveList[moveNum], cps);
4107       /* End of additions by Tord */
4108     }
4109
4110     /* [HGM] setting up the opening has brought engine in force mode! */
4111     /*       Send 'go' if we are in a mode where machine should play. */
4112     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4113         (gameMode == TwoMachinesPlay   ||
4114 #ifdef ZIPPY
4115          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4116 #endif
4117          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4118         SendToProgram("go\n", cps);
4119   if (appData.debugMode) {
4120     fprintf(debugFP, "(extra)\n");
4121   }
4122     }
4123     setboardSpoiledMachineBlack = 0;
4124 }
4125
4126 void
4127 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4128      ChessMove moveType;
4129      int fromX, fromY, toX, toY;
4130 {
4131     char user_move[MSG_SIZ];
4132
4133     switch (moveType) {
4134       default:
4135         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4136                 (int)moveType, fromX, fromY, toX, toY);
4137         DisplayError(user_move + strlen("say "), 0);
4138         break;
4139       case WhiteKingSideCastle:
4140       case BlackKingSideCastle:
4141       case WhiteQueenSideCastleWild:
4142       case BlackQueenSideCastleWild:
4143       /* PUSH Fabien */
4144       case WhiteHSideCastleFR:
4145       case BlackHSideCastleFR:
4146       /* POP Fabien */
4147         sprintf(user_move, "o-o\n");
4148         break;
4149       case WhiteQueenSideCastle:
4150       case BlackQueenSideCastle:
4151       case WhiteKingSideCastleWild:
4152       case BlackKingSideCastleWild:
4153       /* PUSH Fabien */
4154       case WhiteASideCastleFR:
4155       case BlackASideCastleFR:
4156       /* POP Fabien */
4157         sprintf(user_move, "o-o-o\n");
4158         break;
4159       case WhitePromotionQueen:
4160       case BlackPromotionQueen:
4161       case WhitePromotionRook:
4162       case BlackPromotionRook:
4163       case WhitePromotionBishop:
4164       case BlackPromotionBishop:
4165       case WhitePromotionKnight:
4166       case BlackPromotionKnight:
4167       case WhitePromotionKing:
4168       case BlackPromotionKing:
4169       case WhitePromotionChancellor:
4170       case BlackPromotionChancellor:
4171       case WhitePromotionArchbishop:
4172       case BlackPromotionArchbishop:
4173         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4174             sprintf(user_move, "%c%c%c%c=%c\n",
4175                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4176                 PieceToChar(WhiteFerz));
4177         else if(gameInfo.variant == VariantGreat)
4178             sprintf(user_move, "%c%c%c%c=%c\n",
4179                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4180                 PieceToChar(WhiteMan));
4181         else
4182             sprintf(user_move, "%c%c%c%c=%c\n",
4183                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4184                 PieceToChar(PromoPiece(moveType)));
4185         break;
4186       case WhiteDrop:
4187       case BlackDrop:
4188         sprintf(user_move, "%c@%c%c\n",
4189                 ToUpper(PieceToChar((ChessSquare) fromX)),
4190                 AAA + toX, ONE + toY);
4191         break;
4192       case NormalMove:
4193       case WhiteCapturesEnPassant:
4194       case BlackCapturesEnPassant:
4195       case IllegalMove:  /* could be a variant we don't quite understand */
4196         sprintf(user_move, "%c%c%c%c\n",
4197                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4198         break;
4199     }
4200     SendToICS(user_move);
4201     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4202         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4203 }
4204
4205 void
4206 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4207      int rf, ff, rt, ft;
4208      char promoChar;
4209      char move[7];
4210 {
4211     if (rf == DROP_RANK) {
4212         sprintf(move, "%c@%c%c\n",
4213                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4214     } else {
4215         if (promoChar == 'x' || promoChar == NULLCHAR) {
4216             sprintf(move, "%c%c%c%c\n",
4217                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4218         } else {
4219             sprintf(move, "%c%c%c%c%c\n",
4220                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4221         }
4222     }
4223 }
4224
4225 void
4226 ProcessICSInitScript(f)
4227      FILE *f;
4228 {
4229     char buf[MSG_SIZ];
4230
4231     while (fgets(buf, MSG_SIZ, f)) {
4232         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4233     }
4234
4235     fclose(f);
4236 }
4237
4238
4239 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4240 void
4241 AlphaRank(char *move, int n)
4242 {
4243 //    char *p = move, c; int x, y;
4244
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4247     }
4248
4249     if(move[1]=='*' && 
4250        move[2]>='0' && move[2]<='9' &&
4251        move[3]>='a' && move[3]<='x'    ) {
4252         move[1] = '@';
4253         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4254         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4255     } else
4256     if(move[0]>='0' && move[0]<='9' &&
4257        move[1]>='a' && move[1]<='x' &&
4258        move[2]>='0' && move[2]<='9' &&
4259        move[3]>='a' && move[3]<='x'    ) {
4260         /* input move, Shogi -> normal */
4261         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4262         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4263         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4264         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265     } else
4266     if(move[1]=='@' &&
4267        move[3]>='0' && move[3]<='9' &&
4268        move[2]>='a' && move[2]<='x'    ) {
4269         move[1] = '*';
4270         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4271         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4272     } else
4273     if(
4274        move[0]>='a' && move[0]<='x' &&
4275        move[3]>='0' && move[3]<='9' &&
4276        move[2]>='a' && move[2]<='x'    ) {
4277          /* output move, normal -> Shogi */
4278         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4279         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4280         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4281         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4282         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4283     }
4284     if (appData.debugMode) {
4285         fprintf(debugFP, "   out = '%s'\n", move);
4286     }
4287 }
4288
4289 /* Parser for moves from gnuchess, ICS, or user typein box */
4290 Boolean
4291 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4292      char *move;
4293      int moveNum;
4294      ChessMove *moveType;
4295      int *fromX, *fromY, *toX, *toY;
4296      char *promoChar;
4297 {       
4298     if (appData.debugMode) {
4299         fprintf(debugFP, "move to parse: %s\n", move);
4300     }
4301     *moveType = yylexstr(moveNum, move);
4302
4303     switch (*moveType) {
4304       case WhitePromotionChancellor:
4305       case BlackPromotionChancellor:
4306       case WhitePromotionArchbishop:
4307       case BlackPromotionArchbishop:
4308       case WhitePromotionQueen:
4309       case BlackPromotionQueen:
4310       case WhitePromotionRook:
4311       case BlackPromotionRook:
4312       case WhitePromotionBishop:
4313       case BlackPromotionBishop:
4314       case WhitePromotionKnight:
4315       case BlackPromotionKnight:
4316       case WhitePromotionKing:
4317       case BlackPromotionKing:
4318       case NormalMove:
4319       case WhiteCapturesEnPassant:
4320       case BlackCapturesEnPassant:
4321       case WhiteKingSideCastle:
4322       case WhiteQueenSideCastle:
4323       case BlackKingSideCastle:
4324       case BlackQueenSideCastle:
4325       case WhiteKingSideCastleWild:
4326       case WhiteQueenSideCastleWild:
4327       case BlackKingSideCastleWild:
4328       case BlackQueenSideCastleWild:
4329       /* Code added by Tord: */
4330       case WhiteHSideCastleFR:
4331       case WhiteASideCastleFR:
4332       case BlackHSideCastleFR:
4333       case BlackASideCastleFR:
4334       /* End of code added by Tord */
4335       case IllegalMove:         /* bug or odd chess variant */
4336         *fromX = currentMoveString[0] - AAA;
4337         *fromY = currentMoveString[1] - ONE;
4338         *toX = currentMoveString[2] - AAA;
4339         *toY = currentMoveString[3] - ONE;
4340         *promoChar = currentMoveString[4];
4341         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4342             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4343     if (appData.debugMode) {
4344         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4345     }
4346             *fromX = *fromY = *toX = *toY = 0;
4347             return FALSE;
4348         }
4349         if (appData.testLegality) {
4350           return (*moveType != IllegalMove);
4351         } else {
4352           return !(fromX == fromY && toX == toY);
4353         }
4354
4355       case WhiteDrop:
4356       case BlackDrop:
4357         *fromX = *moveType == WhiteDrop ?
4358           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4359           (int) CharToPiece(ToLower(currentMoveString[0]));
4360         *fromY = DROP_RANK;
4361         *toX = currentMoveString[2] - AAA;
4362         *toY = currentMoveString[3] - ONE;
4363         *promoChar = NULLCHAR;
4364         return TRUE;
4365
4366       case AmbiguousMove:
4367       case ImpossibleMove:
4368       case (ChessMove) 0:       /* end of file */
4369       case ElapsedTime:
4370       case Comment:
4371       case PGNTag:
4372       case NAG:
4373       case WhiteWins:
4374       case BlackWins:
4375       case GameIsDrawn:
4376       default:
4377     if (appData.debugMode) {
4378         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4379     }
4380         /* bug? */
4381         *fromX = *fromY = *toX = *toY = 0;
4382         *promoChar = NULLCHAR;
4383         return FALSE;
4384     }
4385 }
4386
4387 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4388 // All positions will have equal probability, but the current method will not provide a unique
4389 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4390 #define DARK 1
4391 #define LITE 2
4392 #define ANY 3
4393
4394 int squaresLeft[4];
4395 int piecesLeft[(int)BlackPawn];
4396 int seed, nrOfShuffles;
4397
4398 void GetPositionNumber()
4399 {       // sets global variable seed
4400         int i;
4401
4402         seed = appData.defaultFrcPosition;
4403         if(seed < 0) { // randomize based on time for negative FRC position numbers
4404                 for(i=0; i<50; i++) seed += random();
4405                 seed = random() ^ random() >> 8 ^ random() << 8;
4406                 if(seed<0) seed = -seed;
4407         }
4408 }
4409
4410 int put(Board board, int pieceType, int rank, int n, int shade)
4411 // put the piece on the (n-1)-th empty squares of the given shade
4412 {
4413         int i;
4414
4415         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4416                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4417                         board[rank][i] = (ChessSquare) pieceType;
4418                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4419                         squaresLeft[ANY]--;
4420                         piecesLeft[pieceType]--; 
4421                         return i;
4422                 }
4423         }
4424         return -1;
4425 }
4426
4427
4428 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4429 // calculate where the next piece goes, (any empty square), and put it there
4430 {
4431         int i;
4432
4433         i = seed % squaresLeft[shade];
4434         nrOfShuffles *= squaresLeft[shade];
4435         seed /= squaresLeft[shade];
4436         put(board, pieceType, rank, i, shade);
4437 }
4438
4439 void AddTwoPieces(Board board, int pieceType, int rank)
4440 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4441 {
4442         int i, n=squaresLeft[ANY], j=n-1, k;
4443
4444         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4445         i = seed % k;  // pick one
4446         nrOfShuffles *= k;
4447         seed /= k;
4448         while(i >= j) i -= j--;
4449         j = n - 1 - j; i += j;
4450         put(board, pieceType, rank, j, ANY);
4451         put(board, pieceType, rank, i, ANY);
4452 }
4453
4454 void SetUpShuffle(Board board, int number)
4455 {
4456         int i, p, first=1;
4457
4458         GetPositionNumber(); nrOfShuffles = 1;
4459
4460         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4461         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4462         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4463
4464         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4465
4466         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4467             p = (int) board[0][i];
4468             if(p < (int) BlackPawn) piecesLeft[p] ++;
4469             board[0][i] = EmptySquare;
4470         }
4471
4472         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4473             // shuffles restricted to allow normal castling put KRR first
4474             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4475                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4476             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4477                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4478             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4479                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4480             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4481                 put(board, WhiteRook, 0, 0, ANY);
4482             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4483         }
4484
4485         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4486             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4487             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4488                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4489                 while(piecesLeft[p] >= 2) {
4490                     AddOnePiece(board, p, 0, LITE);
4491                     AddOnePiece(board, p, 0, DARK);
4492                 }
4493                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4494             }
4495
4496         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4497             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4498             // but we leave King and Rooks for last, to possibly obey FRC restriction
4499             if(p == (int)WhiteRook) continue;
4500             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4501             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4502         }
4503
4504         // now everything is placed, except perhaps King (Unicorn) and Rooks
4505
4506         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4507             // Last King gets castling rights
4508             while(piecesLeft[(int)WhiteUnicorn]) {
4509                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4511             }
4512
4513             while(piecesLeft[(int)WhiteKing]) {
4514                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4515                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4516             }
4517
4518
4519         } else {
4520             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4521             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4522         }
4523
4524         // Only Rooks can be left; simply place them all
4525         while(piecesLeft[(int)WhiteRook]) {
4526                 i = put(board, WhiteRook, 0, 0, ANY);
4527                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4528                         if(first) {
4529                                 first=0;
4530                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4531                         }
4532                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4533                 }
4534         }
4535         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4536             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4537         }
4538
4539         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4540 }
4541
4542 int SetCharTable( char *table, const char * map )
4543 /* [HGM] moved here from winboard.c because of its general usefulness */
4544 /*       Basically a safe strcpy that uses the last character as King */
4545 {
4546     int result = FALSE; int NrPieces;
4547
4548     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4549                     && NrPieces >= 12 && !(NrPieces&1)) {
4550         int i; /* [HGM] Accept even length from 12 to 34 */
4551
4552         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4553         for( i=0; i<NrPieces/2-1; i++ ) {
4554             table[i] = map[i];
4555             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4556         }
4557         table[(int) WhiteKing]  = map[NrPieces/2-1];
4558         table[(int) BlackKing]  = map[NrPieces-1];
4559
4560         result = TRUE;
4561     }
4562
4563     return result;
4564 }
4565
4566 void Prelude(Board board)
4567 {       // [HGM] superchess: random selection of exo-pieces
4568         int i, j, k; ChessSquare p; 
4569         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4570
4571         GetPositionNumber(); // use FRC position number
4572
4573         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4574             SetCharTable(pieceToChar, appData.pieceToCharTable);
4575             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4576                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4577         }
4578
4579         j = seed%4;                 seed /= 4; 
4580         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4581         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4582         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4583         j = seed%3 + (seed%3 >= j); seed /= 3; 
4584         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4585         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4586         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4587         j = seed%3;                 seed /= 3; 
4588         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4589         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4590         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4591         j = seed%2 + (seed%2 >= j); seed /= 2; 
4592         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4593         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4594         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4595         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4596         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4597         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4598         put(board, exoPieces[0],    0, 0, ANY);
4599         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4600 }
4601
4602 void
4603 InitPosition(redraw)
4604      int redraw;
4605 {
4606     ChessSquare (* pieces)[BOARD_SIZE];
4607     int i, j, pawnRow, overrule,
4608     oldx = gameInfo.boardWidth,
4609     oldy = gameInfo.boardHeight,
4610     oldh = gameInfo.holdingsWidth,
4611     oldv = gameInfo.variant;
4612
4613     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4614
4615     /* [AS] Initialize pv info list [HGM] and game status */
4616     {
4617         for( i=0; i<MAX_MOVES; i++ ) {
4618             pvInfoList[i].depth = 0;
4619             epStatus[i]=EP_NONE;
4620             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4621         }
4622
4623         initialRulePlies = 0; /* 50-move counter start */
4624
4625         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4626         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4627     }
4628
4629     
4630     /* [HGM] logic here is completely changed. In stead of full positions */
4631     /* the initialized data only consist of the two backranks. The switch */
4632     /* selects which one we will use, which is than copied to the Board   */
4633     /* initialPosition, which for the rest is initialized by Pawns and    */
4634     /* empty squares. This initial position is then copied to boards[0],  */
4635     /* possibly after shuffling, so that it remains available.            */
4636
4637     gameInfo.holdingsWidth = 0; /* default board sizes */
4638     gameInfo.boardWidth    = 8;
4639     gameInfo.boardHeight   = 8;
4640     gameInfo.holdingsSize  = 0;
4641     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4642     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4643     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4644
4645     switch (gameInfo.variant) {
4646     case VariantFischeRandom:
4647       shuffleOpenings = TRUE;
4648     default:
4649       pieces = FIDEArray;
4650       break;
4651     case VariantShatranj:
4652       pieces = ShatranjArray;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4655       break;
4656     case VariantTwoKings:
4657       pieces = twoKingsArray;
4658       break;
4659     case VariantCapaRandom:
4660       shuffleOpenings = TRUE;
4661     case VariantCapablanca:
4662       pieces = CapablancaArray;
4663       gameInfo.boardWidth = 10;
4664       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4665       break;
4666     case VariantGothic:
4667       pieces = GothicArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4670       break;
4671     case VariantJanus:
4672       pieces = JanusArray;
4673       gameInfo.boardWidth = 10;
4674       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4675       nrCastlingRights = 6;
4676         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4677         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4678         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4679         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4680         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4681         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4682       break;
4683     case VariantFalcon:
4684       pieces = FalconArray;
4685       gameInfo.boardWidth = 10;
4686       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4687       break;
4688     case VariantXiangqi:
4689       pieces = XiangqiArray;
4690       gameInfo.boardWidth  = 9;
4691       gameInfo.boardHeight = 10;
4692       nrCastlingRights = 0;
4693       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4694       break;
4695     case VariantShogi:
4696       pieces = ShogiArray;
4697       gameInfo.boardWidth  = 9;
4698       gameInfo.boardHeight = 9;
4699       gameInfo.holdingsSize = 7;
4700       nrCastlingRights = 0;
4701       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4702       break;
4703     case VariantCourier:
4704       pieces = CourierArray;
4705       gameInfo.boardWidth  = 12;
4706       nrCastlingRights = 0;
4707       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4708       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4709       break;
4710     case VariantKnightmate:
4711       pieces = KnightmateArray;
4712       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4713       break;
4714     case VariantFairy:
4715       pieces = fairyArray;
4716       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4717       break;
4718     case VariantGreat:
4719       pieces = GreatArray;
4720       gameInfo.boardWidth = 10;
4721       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4722       gameInfo.holdingsSize = 8;
4723       break;
4724     case VariantSuper:
4725       pieces = FIDEArray;
4726       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4727       gameInfo.holdingsSize = 8;
4728       startedFromSetupPosition = TRUE;
4729       break;
4730     case VariantCrazyhouse:
4731     case VariantBughouse:
4732       pieces = FIDEArray;
4733       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4734       gameInfo.holdingsSize = 5;
4735       break;
4736     case VariantWildCastle:
4737       pieces = FIDEArray;
4738       /* !!?shuffle with kings guaranteed to be on d or e file */
4739       shuffleOpenings = 1;
4740       break;
4741     case VariantNoCastle:
4742       pieces = FIDEArray;
4743       nrCastlingRights = 0;
4744       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4745       /* !!?unconstrained back-rank shuffle */
4746       shuffleOpenings = 1;
4747       break;
4748     }
4749
4750     overrule = 0;
4751     if(appData.NrFiles >= 0) {
4752         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4753         gameInfo.boardWidth = appData.NrFiles;
4754     }
4755     if(appData.NrRanks >= 0) {
4756         gameInfo.boardHeight = appData.NrRanks;
4757     }
4758     if(appData.holdingsSize >= 0) {
4759         i = appData.holdingsSize;
4760         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4761         gameInfo.holdingsSize = i;
4762     }
4763     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4764     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4765         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4766
4767     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4768     if(pawnRow < 1) pawnRow = 1;
4769
4770     /* User pieceToChar list overrules defaults */
4771     if(appData.pieceToCharTable != NULL)
4772         SetCharTable(pieceToChar, appData.pieceToCharTable);
4773
4774     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4775
4776         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4777             s = (ChessSquare) 0; /* account holding counts in guard band */
4778         for( i=0; i<BOARD_HEIGHT; i++ )
4779             initialPosition[i][j] = s;
4780
4781         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4782         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4783         initialPosition[pawnRow][j] = WhitePawn;
4784         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4785         if(gameInfo.variant == VariantXiangqi) {
4786             if(j&1) {
4787                 initialPosition[pawnRow][j] = 
4788                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4789                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4790                    initialPosition[2][j] = WhiteCannon;
4791                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4792                 }
4793             }
4794         }
4795         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4796     }
4797     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4798
4799             j=BOARD_LEFT+1;
4800             initialPosition[1][j] = WhiteBishop;
4801             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4802             j=BOARD_RGHT-2;
4803             initialPosition[1][j] = WhiteRook;
4804             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4805     }
4806
4807     if( nrCastlingRights == -1) {
4808         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4809         /*       This sets default castling rights from none to normal corners   */
4810         /* Variants with other castling rights must set them themselves above    */
4811         nrCastlingRights = 6;
4812        
4813         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4814         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4815         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4816         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4817         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4818         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4819      }
4820
4821      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4822      if(gameInfo.variant == VariantGreat) { // promotion commoners
4823         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4824         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4825         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4826         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4827      }
4828   if (appData.debugMode) {
4829     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4830   }
4831     if(shuffleOpenings) {
4832         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4833         startedFromSetupPosition = TRUE;
4834     }
4835     if(startedFromPositionFile) {
4836       /* [HGM] loadPos: use PositionFile for every new game */
4837       CopyBoard(initialPosition, filePosition);
4838       for(i=0; i<nrCastlingRights; i++)
4839           castlingRights[0][i] = initialRights[i] = fileRights[i];
4840       startedFromSetupPosition = TRUE;
4841     }
4842
4843     CopyBoard(boards[0], initialPosition);
4844
4845     if(oldx != gameInfo.boardWidth ||
4846        oldy != gameInfo.boardHeight ||
4847        oldh != gameInfo.holdingsWidth
4848 #ifdef GOTHIC
4849        || oldv == VariantGothic ||        // For licensing popups
4850        gameInfo.variant == VariantGothic
4851 #endif
4852 #ifdef FALCON
4853        || oldv == VariantFalcon ||
4854        gameInfo.variant == VariantFalcon
4855 #endif
4856                                          )
4857             InitDrawingSizes(-2 ,0);
4858
4859     if (redraw)
4860       DrawPosition(TRUE, boards[currentMove]);
4861 }
4862
4863 void
4864 SendBoard(cps, moveNum)
4865      ChessProgramState *cps;
4866      int moveNum;
4867 {
4868     char message[MSG_SIZ];
4869     
4870     if (cps->useSetboard) {
4871       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4872       sprintf(message, "setboard %s\n", fen);
4873       SendToProgram(message, cps);
4874       free(fen);
4875
4876     } else {
4877       ChessSquare *bp;
4878       int i, j;
4879       /* Kludge to set black to move, avoiding the troublesome and now
4880        * deprecated "black" command.
4881        */
4882       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4883
4884       SendToProgram("edit\n", cps);
4885       SendToProgram("#\n", cps);
4886       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4887         bp = &boards[moveNum][i][BOARD_LEFT];
4888         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4889           if ((int) *bp < (int) BlackPawn) {
4890             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4891                     AAA + j, ONE + i);
4892             if(message[0] == '+' || message[0] == '~') {
4893                 sprintf(message, "%c%c%c+\n",
4894                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4895                         AAA + j, ONE + i);
4896             }
4897             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4898                 message[1] = BOARD_RGHT   - 1 - j + '1';
4899                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4900             }
4901             SendToProgram(message, cps);
4902           }
4903         }
4904       }
4905     
4906       SendToProgram("c\n", cps);
4907       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4908         bp = &boards[moveNum][i][BOARD_LEFT];
4909         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4910           if (((int) *bp != (int) EmptySquare)
4911               && ((int) *bp >= (int) BlackPawn)) {
4912             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4913                     AAA + j, ONE + i);
4914             if(message[0] == '+' || message[0] == '~') {
4915                 sprintf(message, "%c%c%c+\n",
4916                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4917                         AAA + j, ONE + i);
4918             }
4919             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4920                 message[1] = BOARD_RGHT   - 1 - j + '1';
4921                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4922             }
4923             SendToProgram(message, cps);
4924           }
4925         }
4926       }
4927     
4928       SendToProgram(".\n", cps);
4929     }
4930     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4931 }
4932
4933 int
4934 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4935 {
4936     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4937     /* [HGM] add Shogi promotions */
4938     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4939     ChessSquare piece;
4940     ChessMove moveType;
4941     Boolean premove;
4942
4943     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4944     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4945
4946     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4947       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4948         return FALSE;
4949
4950     piece = boards[currentMove][fromY][fromX];
4951     if(gameInfo.variant == VariantShogi) {
4952         promotionZoneSize = 3;
4953         highestPromotingPiece = (int)WhiteFerz;
4954     }
4955
4956     // next weed out all moves that do not touch the promotion zone at all
4957     if((int)piece >= BlackPawn) {
4958         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4959              return FALSE;
4960         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4961     } else {
4962         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4963            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4964     }
4965
4966     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4967
4968     // weed out mandatory Shogi promotions
4969     if(gameInfo.variant == VariantShogi) {
4970         if(piece >= BlackPawn) {
4971             if(toY == 0 && piece == BlackPawn ||
4972                toY == 0 && piece == BlackQueen ||
4973                toY <= 1 && piece == BlackKnight) {
4974                 *promoChoice = '+';
4975                 return FALSE;
4976             }
4977         } else {
4978             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4979                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4980                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4981                 *promoChoice = '+';
4982                 return FALSE;
4983             }
4984         }
4985     }
4986
4987     // weed out obviously illegal Pawn moves
4988     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4989         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4990         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4991         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4992         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4993         // note we are not allowed to test for valid (non-)capture, due to premove
4994     }
4995
4996     // we either have a choice what to promote to, or (in Shogi) whether to promote
4997     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4998         *promoChoice = PieceToChar(BlackFerz);  // no choice
4999         return FALSE;
5000     }
5001     if(appData.alwaysPromoteToQueen) { // predetermined
5002         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5003              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5004         else *promoChoice = PieceToChar(BlackQueen);
5005         return FALSE;
5006     }
5007
5008     // suppress promotion popup on illegal moves that are not premoves
5009     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5010               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5011     if(appData.testLegality && !premove) {
5012         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5013                         epStatus[currentMove], castlingRights[currentMove],
5014                         fromY, fromX, toY, toX, NULLCHAR);
5015         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5016            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5017             return FALSE;
5018     }
5019
5020     return TRUE;
5021 }
5022
5023 int
5024 InPalace(row, column)
5025      int row, column;
5026 {   /* [HGM] for Xiangqi */
5027     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5028          column < (BOARD_WIDTH + 4)/2 &&
5029          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5030     return FALSE;
5031 }
5032
5033 int
5034 PieceForSquare (x, y)
5035      int x;
5036      int y;
5037 {
5038   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5039      return -1;
5040   else
5041      return boards[currentMove][y][x];
5042 }
5043
5044 int
5045 OKToStartUserMove(x, y)
5046      int x, y;
5047 {
5048     ChessSquare from_piece;
5049     int white_piece;
5050
5051     if (matchMode) return FALSE;
5052     if (gameMode == EditPosition) return TRUE;
5053
5054     if (x >= 0 && y >= 0)
5055       from_piece = boards[currentMove][y][x];
5056     else
5057       from_piece = EmptySquare;
5058
5059     if (from_piece == EmptySquare) return FALSE;
5060
5061     white_piece = (int)from_piece >= (int)WhitePawn &&
5062       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5063
5064     switch (gameMode) {
5065       case PlayFromGameFile:
5066       case AnalyzeFile:
5067       case TwoMachinesPlay:
5068       case EndOfGame:
5069         return FALSE;
5070
5071       case IcsObserving:
5072       case IcsIdle:
5073         return FALSE;
5074
5075       case MachinePlaysWhite:
5076       case IcsPlayingBlack:
5077         if (appData.zippyPlay) return FALSE;
5078         if (white_piece) {
5079             DisplayMoveError(_("You are playing Black"));
5080             return FALSE;
5081         }
5082         break;
5083
5084       case MachinePlaysBlack:
5085       case IcsPlayingWhite:
5086         if (appData.zippyPlay) return FALSE;
5087         if (!white_piece) {
5088             DisplayMoveError(_("You are playing White"));
5089             return FALSE;
5090         }
5091         break;
5092
5093       case EditGame:
5094         if (!white_piece && WhiteOnMove(currentMove)) {
5095             DisplayMoveError(_("It is White's turn"));
5096             return FALSE;
5097         }           
5098         if (white_piece && !WhiteOnMove(currentMove)) {
5099             DisplayMoveError(_("It is Black's turn"));
5100             return FALSE;
5101         }           
5102         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5103             /* Editing correspondence game history */
5104             /* Could disallow this or prompt for confirmation */
5105             cmailOldMove = -1;
5106         }
5107         if (currentMove < forwardMostMove) {
5108             /* Discarding moves */
5109             /* Could prompt for confirmation here,
5110                but I don't think that's such a good idea */
5111             forwardMostMove = currentMove;
5112         }
5113         break;
5114
5115       case BeginningOfGame:
5116         if (appData.icsActive) return FALSE;
5117         if (!appData.noChessProgram) {
5118             if (!white_piece) {
5119                 DisplayMoveError(_("You are playing White"));
5120                 return FALSE;
5121             }
5122         }
5123         break;
5124         
5125       case Training:
5126         if (!white_piece && WhiteOnMove(currentMove)) {
5127             DisplayMoveError(_("It is White's turn"));
5128             return FALSE;
5129         }           
5130         if (white_piece && !WhiteOnMove(currentMove)) {
5131             DisplayMoveError(_("It is Black's turn"));
5132             return FALSE;
5133         }           
5134         break;
5135
5136       default:
5137       case IcsExamining:
5138         break;
5139     }
5140     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5141         && gameMode != AnalyzeFile && gameMode != Training) {
5142         DisplayMoveError(_("Displayed position is not current"));
5143         return FALSE;
5144     }
5145     return TRUE;
5146 }
5147
5148 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5149 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5150 int lastLoadGameUseList = FALSE;
5151 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5152 ChessMove lastLoadGameStart = (ChessMove) 0;
5153
5154 ChessMove
5155 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5156      int fromX, fromY, toX, toY;
5157      int promoChar;
5158      Boolean captureOwn;
5159 {
5160     ChessMove moveType;
5161     ChessSquare pdown, pup;
5162
5163     /* Check if the user is playing in turn.  This is complicated because we
5164        let the user "pick up" a piece before it is his turn.  So the piece he
5165        tried to pick up may have been captured by the time he puts it down!
5166        Therefore we use the color the user is supposed to be playing in this
5167        test, not the color of the piece that is currently on the starting
5168        square---except in EditGame mode, where the user is playing both
5169        sides; fortunately there the capture race can't happen.  (It can
5170        now happen in IcsExamining mode, but that's just too bad.  The user
5171        will get a somewhat confusing message in that case.)
5172        */
5173
5174     switch (gameMode) {
5175       case PlayFromGameFile:
5176       case AnalyzeFile:
5177       case TwoMachinesPlay:
5178       case EndOfGame:
5179       case IcsObserving:
5180       case IcsIdle:
5181         /* We switched into a game mode where moves are not accepted,
5182            perhaps while the mouse button was down. */
5183         return ImpossibleMove;
5184
5185       case MachinePlaysWhite:
5186         /* User is moving for Black */
5187         if (WhiteOnMove(currentMove)) {
5188             DisplayMoveError(_("It is White's turn"));
5189             return ImpossibleMove;
5190         }
5191         break;
5192
5193       case MachinePlaysBlack:
5194         /* User is moving for White */
5195         if (!WhiteOnMove(currentMove)) {
5196             DisplayMoveError(_("It is Black's turn"));
5197             return ImpossibleMove;
5198         }
5199         break;
5200
5201       case EditGame:
5202       case IcsExamining:
5203       case BeginningOfGame:
5204       case AnalyzeMode:
5205       case Training:
5206         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5207             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5208             /* User is moving for Black */
5209             if (WhiteOnMove(currentMove)) {
5210                 DisplayMoveError(_("It is White's turn"));
5211                 return ImpossibleMove;
5212             }
5213         } else {
5214             /* User is moving for White */
5215             if (!WhiteOnMove(currentMove)) {
5216                 DisplayMoveError(_("It is Black's turn"));
5217                 return ImpossibleMove;
5218             }
5219         }
5220         break;
5221
5222       case IcsPlayingBlack:
5223         /* User is moving for Black */
5224         if (WhiteOnMove(currentMove)) {
5225             if (!appData.premove) {
5226                 DisplayMoveError(_("It is White's turn"));
5227             } else if (toX >= 0 && toY >= 0) {
5228                 premoveToX = toX;
5229                 premoveToY = toY;
5230                 premoveFromX = fromX;
5231                 premoveFromY = fromY;
5232                 premovePromoChar = promoChar;
5233                 gotPremove = 1;
5234                 if (appData.debugMode) 
5235                     fprintf(debugFP, "Got premove: fromX %d,"
5236                             "fromY %d, toX %d, toY %d\n",
5237                             fromX, fromY, toX, toY);
5238             }
5239             return ImpossibleMove;
5240         }
5241         break;
5242
5243       case IcsPlayingWhite:
5244         /* User is moving for White */
5245         if (!WhiteOnMove(currentMove)) {
5246             if (!appData.premove) {
5247                 DisplayMoveError(_("It is Black's turn"));
5248             } else if (toX >= 0 && toY >= 0) {
5249                 premoveToX = toX;
5250                 premoveToY = toY;
5251                 premoveFromX = fromX;
5252                 premoveFromY = fromY;
5253                 premovePromoChar = promoChar;
5254                 gotPremove = 1;
5255                 if (appData.debugMode) 
5256                     fprintf(debugFP, "Got premove: fromX %d,"
5257                             "fromY %d, toX %d, toY %d\n",
5258                             fromX, fromY, toX, toY);
5259             }
5260             return ImpossibleMove;
5261         }
5262         break;
5263
5264       default:
5265         break;
5266
5267       case EditPosition:
5268         /* EditPosition, empty square, or different color piece;
5269            click-click move is possible */
5270         if (toX == -2 || toY == -2) {
5271             boards[0][fromY][fromX] = EmptySquare;
5272             return AmbiguousMove;
5273         } else if (toX >= 0 && toY >= 0) {
5274             boards[0][toY][toX] = boards[0][fromY][fromX];
5275             boards[0][fromY][fromX] = EmptySquare;
5276             return AmbiguousMove;
5277         }
5278         return ImpossibleMove;
5279     }
5280
5281     if(toX < 0 || toY < 0) return ImpossibleMove;
5282     pdown = boards[currentMove][fromY][fromX];
5283     pup = boards[currentMove][toY][toX];
5284
5285     /* [HGM] If move started in holdings, it means a drop */
5286     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5287          if( pup != EmptySquare ) return ImpossibleMove;
5288          if(appData.testLegality) {
5289              /* it would be more logical if LegalityTest() also figured out
5290               * which drops are legal. For now we forbid pawns on back rank.
5291               * Shogi is on its own here...
5292               */
5293              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5294                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5295                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5296          }
5297          return WhiteDrop; /* Not needed to specify white or black yet */
5298     }
5299
5300     userOfferedDraw = FALSE;
5301         
5302     /* [HGM] always test for legality, to get promotion info */
5303     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5304                           epStatus[currentMove], castlingRights[currentMove],
5305                                          fromY, fromX, toY, toX, promoChar);
5306     /* [HGM] but possibly ignore an IllegalMove result */
5307     if (appData.testLegality) {
5308         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5309             DisplayMoveError(_("Illegal move"));
5310             return ImpossibleMove;
5311         }
5312     }
5313
5314     return moveType;
5315     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5316        function is made into one that returns an OK move type if FinishMove
5317        should be called. This to give the calling driver routine the
5318        opportunity to finish the userMove input with a promotion popup,
5319        without bothering the user with this for invalid or illegal moves */
5320
5321 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5322 }
5323
5324 /* Common tail of UserMoveEvent and DropMenuEvent */
5325 int
5326 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5327      ChessMove moveType;
5328      int fromX, fromY, toX, toY;
5329      /*char*/int promoChar;
5330 {
5331     char *bookHit = 0;
5332
5333     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5334         // [HGM] superchess: suppress promotions to non-available piece
5335         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5336         if(WhiteOnMove(currentMove)) {
5337             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5338         } else {
5339             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5340         }
5341     }
5342
5343     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5344        move type in caller when we know the move is a legal promotion */
5345     if(moveType == NormalMove && promoChar)
5346         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5347
5348     /* [HGM] convert drag-and-drop piece drops to standard form */
5349     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5350          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5351            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5352                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5353            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5354            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5355            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5356            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5357          fromY = DROP_RANK;
5358     }
5359
5360     /* [HGM] <popupFix> The following if has been moved here from
5361        UserMoveEvent(). Because it seemed to belong here (why not allow
5362        piece drops in training games?), and because it can only be
5363        performed after it is known to what we promote. */
5364     if (gameMode == Training) {
5365       /* compare the move played on the board to the next move in the
5366        * game. If they match, display the move and the opponent's response. 
5367        * If they don't match, display an error message.
5368        */
5369       int saveAnimate;
5370       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5371       CopyBoard(testBoard, boards[currentMove]);
5372       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5373
5374       if (CompareBoards(testBoard, boards[currentMove+1])) {
5375         ForwardInner(currentMove+1);
5376
5377         /* Autoplay the opponent's response.
5378          * if appData.animate was TRUE when Training mode was entered,
5379          * the response will be animated.
5380          */
5381         saveAnimate = appData.animate;
5382         appData.animate = animateTraining;
5383         ForwardInner(currentMove+1);
5384         appData.animate = saveAnimate;
5385
5386         /* check for the end of the game */
5387         if (currentMove >= forwardMostMove) {
5388           gameMode = PlayFromGameFile;
5389           ModeHighlight();
5390           SetTrainingModeOff();
5391           DisplayInformation(_("End of game"));
5392         }
5393       } else {
5394         DisplayError(_("Incorrect move"), 0);
5395       }
5396       return 1;
5397     }
5398
5399   /* Ok, now we know that the move is good, so we can kill
5400      the previous line in Analysis Mode */
5401   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5402     forwardMostMove = currentMove;
5403   }
5404
5405   /* If we need the chess program but it's dead, restart it */
5406   ResurrectChessProgram();
5407
5408   /* A user move restarts a paused game*/
5409   if (pausing)
5410     PauseEvent();
5411
5412   thinkOutput[0] = NULLCHAR;
5413
5414   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5415
5416   if (gameMode == BeginningOfGame) {
5417     if (appData.noChessProgram) {
5418       gameMode = EditGame;
5419       SetGameInfo();
5420     } else {
5421       char buf[MSG_SIZ];
5422       gameMode = MachinePlaysBlack;
5423       StartClocks();
5424       SetGameInfo();
5425       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5426       DisplayTitle(buf);
5427       if (first.sendName) {
5428         sprintf(buf, "name %s\n", gameInfo.white);
5429         SendToProgram(buf, &first);
5430       }
5431       StartClocks();
5432     }
5433     ModeHighlight();
5434   }
5435
5436   /* Relay move to ICS or chess engine */
5437   if (appData.icsActive) {
5438     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5439         gameMode == IcsExamining) {
5440       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5441       ics_user_moved = 1;
5442     }
5443   } else {
5444     if (first.sendTime && (gameMode == BeginningOfGame ||
5445                            gameMode == MachinePlaysWhite ||
5446                            gameMode == MachinePlaysBlack)) {
5447       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5448     }
5449     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5450          // [HGM] book: if program might be playing, let it use book
5451         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5452         first.maybeThinking = TRUE;
5453     } else SendMoveToProgram(forwardMostMove-1, &first);
5454     if (currentMove == cmailOldMove + 1) {
5455       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5456     }
5457   }
5458
5459   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5460
5461   switch (gameMode) {
5462   case EditGame:
5463     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5464                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5465     case MT_NONE:
5466     case MT_CHECK:
5467       break;
5468     case MT_CHECKMATE:
5469     case MT_STAINMATE:
5470       if (WhiteOnMove(currentMove)) {
5471         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5472       } else {
5473         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5474       }
5475       break;
5476     case MT_STALEMATE:
5477       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5478       break;
5479     }
5480     break;
5481     
5482   case MachinePlaysBlack:
5483   case MachinePlaysWhite:
5484     /* disable certain menu options while machine is thinking */
5485     SetMachineThinkingEnables();
5486     break;
5487
5488   default:
5489     break;
5490   }
5491
5492   if(bookHit) { // [HGM] book: simulate book reply
5493         static char bookMove[MSG_SIZ]; // a bit generous?
5494
5495         programStats.nodes = programStats.depth = programStats.time = 
5496         programStats.score = programStats.got_only_move = 0;
5497         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5498
5499         strcpy(bookMove, "move ");
5500         strcat(bookMove, bookHit);
5501         HandleMachineMove(bookMove, &first);
5502   }
5503   return 1;
5504 }
5505
5506 void
5507 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5508      int fromX, fromY, toX, toY;
5509      int promoChar;
5510 {
5511     /* [HGM] This routine was added to allow calling of its two logical
5512        parts from other modules in the old way. Before, UserMoveEvent()
5513        automatically called FinishMove() if the move was OK, and returned
5514        otherwise. I separated the two, in order to make it possible to
5515        slip a promotion popup in between. But that it always needs two
5516        calls, to the first part, (now called UserMoveTest() ), and to
5517        FinishMove if the first part succeeded. Calls that do not need
5518        to do anything in between, can call this routine the old way. 
5519     */
5520     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5521 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5522     if(moveType == AmbiguousMove)
5523         DrawPosition(FALSE, boards[currentMove]);
5524     else if(moveType != ImpossibleMove && moveType != Comment)
5525         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5526 }
5527
5528 void LeftClick(ClickType clickType, int xPix, int yPix)
5529 {
5530     int x, y;
5531     Boolean saveAnimate;
5532     static int second = 0, promotionChoice = 0;
5533     char promoChoice = NULLCHAR;
5534
5535     if (clickType == Press) ErrorPopDown();
5536
5537     x = EventToSquare(xPix, BOARD_WIDTH);
5538     y = EventToSquare(yPix, BOARD_HEIGHT);
5539     if (!flipView && y >= 0) {
5540         y = BOARD_HEIGHT - 1 - y;
5541     }
5542     if (flipView && x >= 0) {
5543         x = BOARD_WIDTH - 1 - x;
5544     }
5545
5546     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5547         if(clickType == Release) return; // ignore upclick of click-click destination
5548         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5549         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5550         if(gameInfo.holdingsWidth && 
5551                 (WhiteOnMove(currentMove) 
5552                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5553                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5554             // click in right holdings, for determining promotion piece
5555             ChessSquare p = boards[currentMove][y][x];
5556             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5557             if(p != EmptySquare) {
5558                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5559                 fromX = fromY = -1;
5560                 return;
5561             }
5562         }
5563         DrawPosition(FALSE, boards[currentMove]);
5564         return;
5565     }
5566
5567     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5568     if(clickType == Press
5569             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5570               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5571               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5572         return;
5573
5574     if (fromX == -1) {
5575         if (clickType == Press) {
5576             /* First square */
5577             if (OKToStartUserMove(x, y)) {
5578                 fromX = x;
5579                 fromY = y;
5580                 second = 0;
5581                 DragPieceBegin(xPix, yPix);
5582                 if (appData.highlightDragging) {
5583                     SetHighlights(x, y, -1, -1);
5584                 }
5585             }
5586         }
5587         return;
5588     }
5589
5590     /* fromX != -1 */
5591     if (clickType == Press && gameMode != EditPosition) {
5592         ChessSquare fromP;
5593         ChessSquare toP;
5594         int frc;
5595
5596         // ignore off-board to clicks
5597         if(y < 0 || x < 0) return;
5598
5599         /* Check if clicking again on the same color piece */
5600         fromP = boards[currentMove][fromY][fromX];
5601         toP = boards[currentMove][y][x];
5602         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5603         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5604              WhitePawn <= toP && toP <= WhiteKing &&
5605              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5606              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5607             (BlackPawn <= fromP && fromP <= BlackKing && 
5608              BlackPawn <= toP && toP <= BlackKing &&
5609              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5610              !(fromP == BlackKing && toP == BlackRook && frc))) {
5611             /* Clicked again on same color piece -- changed his mind */
5612             second = (x == fromX && y == fromY);
5613             if (appData.highlightDragging) {
5614                 SetHighlights(x, y, -1, -1);
5615             } else {
5616                 ClearHighlights();
5617             }
5618             if (OKToStartUserMove(x, y)) {
5619                 fromX = x;
5620                 fromY = y;
5621                 DragPieceBegin(xPix, yPix);
5622             }
5623             return;
5624         }
5625         // ignore clicks on holdings
5626         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5627     }
5628
5629     if (clickType == Release && x == fromX && y == fromY) {
5630         DragPieceEnd(xPix, yPix);
5631         if (appData.animateDragging) {
5632             /* Undo animation damage if any */
5633             DrawPosition(FALSE, NULL);
5634         }
5635         if (second) {
5636             /* Second up/down in same square; just abort move */
5637             second = 0;
5638             fromX = fromY = -1;
5639             ClearHighlights();
5640             gotPremove = 0;
5641             ClearPremoveHighlights();
5642         } else {
5643             /* First upclick in same square; start click-click mode */
5644             SetHighlights(x, y, -1, -1);
5645         }
5646         return;
5647     }
5648
5649     /* we now have a different from- and (possibly off-board) to-square */
5650     /* Completed move */
5651     toX = x;
5652     toY = y;
5653     saveAnimate = appData.animate;
5654     if (clickType == Press) {
5655         /* Finish clickclick move */
5656         if (appData.animate || appData.highlightLastMove) {
5657             SetHighlights(fromX, fromY, toX, toY);
5658         } else {
5659             ClearHighlights();
5660         }
5661     } else {
5662         /* Finish drag move */
5663         if (appData.highlightLastMove) {
5664             SetHighlights(fromX, fromY, toX, toY);
5665         } else {
5666             ClearHighlights();
5667         }
5668         DragPieceEnd(xPix, yPix);
5669         /* Don't animate move and drag both */
5670         appData.animate = FALSE;
5671     }
5672
5673     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5674     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5675         ClearHighlights();
5676         fromX = fromY = -1;
5677         DrawPosition(TRUE, NULL);
5678         return;
5679     }
5680
5681     // off-board moves should not be highlighted
5682     if(x < 0 || x < 0) ClearHighlights();
5683
5684     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5685         SetHighlights(fromX, fromY, toX, toY);
5686         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5687             // [HGM] super: promotion to captured piece selected from holdings
5688             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5689             promotionChoice = TRUE;
5690             // kludge follows to temporarily execute move on display, without promoting yet
5691             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5692             boards[currentMove][toY][toX] = p;
5693             DrawPosition(FALSE, boards[currentMove]);
5694             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5695             boards[currentMove][toY][toX] = q;
5696             DisplayMessage("Click in holdings to choose piece", "");
5697             return;
5698         }
5699         PromotionPopUp();
5700     } else {
5701         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5702         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5703         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5704         fromX = fromY = -1;
5705     }
5706     appData.animate = saveAnimate;
5707     if (appData.animate || appData.animateDragging) {
5708         /* Undo animation damage if needed */
5709         DrawPosition(FALSE, NULL);
5710     }
5711 }
5712
5713 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5714 {
5715 //    char * hint = lastHint;
5716     FrontEndProgramStats stats;
5717
5718     stats.which = cps == &first ? 0 : 1;
5719     stats.depth = cpstats->depth;
5720     stats.nodes = cpstats->nodes;
5721     stats.score = cpstats->score;
5722     stats.time = cpstats->time;
5723     stats.pv = cpstats->movelist;
5724     stats.hint = lastHint;
5725     stats.an_move_index = 0;
5726     stats.an_move_count = 0;
5727
5728     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5729         stats.hint = cpstats->move_name;
5730         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5731         stats.an_move_count = cpstats->nr_moves;
5732     }
5733
5734     SetProgramStats( &stats );
5735 }
5736
5737 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5738 {   // [HGM] book: this routine intercepts moves to simulate book replies
5739     char *bookHit = NULL;
5740
5741     //first determine if the incoming move brings opponent into his book
5742     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5743         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5744     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5745     if(bookHit != NULL && !cps->bookSuspend) {
5746         // make sure opponent is not going to reply after receiving move to book position
5747         SendToProgram("force\n", cps);
5748         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5749     }
5750     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5751     // now arrange restart after book miss
5752     if(bookHit) {
5753         // after a book hit we never send 'go', and the code after the call to this routine
5754         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5755         char buf[MSG_SIZ];
5756         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5757         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5758         SendToProgram(buf, cps);
5759         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5760     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5761         SendToProgram("go\n", cps);
5762         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5763     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5764         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5765             SendToProgram("go\n", cps); 
5766         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5767     }
5768     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5769 }
5770
5771 char *savedMessage;
5772 ChessProgramState *savedState;
5773 void DeferredBookMove(void)
5774 {
5775         if(savedState->lastPing != savedState->lastPong)
5776                     ScheduleDelayedEvent(DeferredBookMove, 10);
5777         else
5778         HandleMachineMove(savedMessage, savedState);
5779 }
5780
5781 void
5782 HandleMachineMove(message, cps)
5783      char *message;
5784      ChessProgramState *cps;
5785 {
5786     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5787     char realname[MSG_SIZ];
5788     int fromX, fromY, toX, toY;
5789     ChessMove moveType;
5790     char promoChar;
5791     char *p;
5792     int machineWhite;
5793     char *bookHit;
5794
5795 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5796     /*
5797      * Kludge to ignore BEL characters
5798      */
5799     while (*message == '\007') message++;
5800
5801     /*
5802      * [HGM] engine debug message: ignore lines starting with '#' character
5803      */
5804     if(cps->debug && *message == '#') return;
5805
5806     /*
5807      * Look for book output
5808      */
5809     if (cps == &first && bookRequested) {
5810         if (message[0] == '\t' || message[0] == ' ') {
5811             /* Part of the book output is here; append it */
5812             strcat(bookOutput, message);
5813             strcat(bookOutput, "  \n");
5814             return;
5815         } else if (bookOutput[0] != NULLCHAR) {
5816             /* All of book output has arrived; display it */
5817             char *p = bookOutput;
5818             while (*p != NULLCHAR) {
5819                 if (*p == '\t') *p = ' ';
5820                 p++;
5821             }
5822             DisplayInformation(bookOutput);
5823             bookRequested = FALSE;
5824             /* Fall through to parse the current output */
5825         }
5826     }
5827
5828     /*
5829      * Look for machine move.
5830      */
5831     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5832         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5833     {
5834         /* This method is only useful on engines that support ping */
5835         if (cps->lastPing != cps->lastPong) {
5836           if (gameMode == BeginningOfGame) {
5837             /* Extra move from before last new; ignore */
5838             if (appData.debugMode) {
5839                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5840             }
5841           } else {
5842             if (appData.debugMode) {
5843                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5844                         cps->which, gameMode);
5845             }
5846
5847             SendToProgram("undo\n", cps);
5848           }
5849           return;
5850         }
5851
5852         switch (gameMode) {
5853           case BeginningOfGame:
5854             /* Extra move from before last reset; ignore */
5855             if (appData.debugMode) {
5856                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5857             }
5858             return;
5859
5860           case EndOfGame:
5861           case IcsIdle:
5862           default:
5863             /* Extra move after we tried to stop.  The mode test is
5864                not a reliable way of detecting this problem, but it's
5865                the best we can do on engines that don't support ping.
5866             */
5867             if (appData.debugMode) {
5868                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5869                         cps->which, gameMode);
5870             }
5871             SendToProgram("undo\n", cps);
5872             return;
5873
5874           case MachinePlaysWhite:
5875           case IcsPlayingWhite:
5876             machineWhite = TRUE;
5877             break;
5878
5879           case MachinePlaysBlack:
5880           case IcsPlayingBlack:
5881             machineWhite = FALSE;
5882             break;
5883
5884           case TwoMachinesPlay:
5885             machineWhite = (cps->twoMachinesColor[0] == 'w');
5886             break;
5887         }
5888         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5889             if (appData.debugMode) {
5890                 fprintf(debugFP,
5891                         "Ignoring move out of turn by %s, gameMode %d"
5892                         ", forwardMost %d\n",
5893                         cps->which, gameMode, forwardMostMove);
5894             }
5895             return;
5896         }
5897
5898     if (appData.debugMode) { int f = forwardMostMove;
5899         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5900                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5901     }
5902         if(cps->alphaRank) AlphaRank(machineMove, 4);
5903         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5904                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5905             /* Machine move could not be parsed; ignore it. */
5906             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5907                     machineMove, cps->which);
5908             DisplayError(buf1, 0);
5909             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5910                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5911             if (gameMode == TwoMachinesPlay) {
5912               GameEnds(machineWhite ? BlackWins : WhiteWins,
5913                        buf1, GE_XBOARD);
5914             }
5915             return;
5916         }
5917
5918         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5919         /* So we have to redo legality test with true e.p. status here,  */
5920         /* to make sure an illegal e.p. capture does not slip through,   */
5921         /* to cause a forfeit on a justified illegal-move complaint      */
5922         /* of the opponent.                                              */
5923         if( gameMode==TwoMachinesPlay && appData.testLegality
5924             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5925                                                               ) {
5926            ChessMove moveType;
5927            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5928                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5929                              fromY, fromX, toY, toX, promoChar);
5930             if (appData.debugMode) {
5931                 int i;
5932                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5933                     castlingRights[forwardMostMove][i], castlingRank[i]);
5934                 fprintf(debugFP, "castling rights\n");
5935             }
5936             if(moveType == IllegalMove) {
5937                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5938                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5939                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5940                            buf1, GE_XBOARD);
5941                 return;
5942            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5943            /* [HGM] Kludge to handle engines that send FRC-style castling
5944               when they shouldn't (like TSCP-Gothic) */
5945            switch(moveType) {
5946              case WhiteASideCastleFR:
5947              case BlackASideCastleFR:
5948                toX+=2;
5949                currentMoveString[2]++;
5950                break;
5951              case WhiteHSideCastleFR:
5952              case BlackHSideCastleFR:
5953                toX--;
5954                currentMoveString[2]--;
5955                break;
5956              default: ; // nothing to do, but suppresses warning of pedantic compilers
5957            }
5958         }
5959         hintRequested = FALSE;
5960         lastHint[0] = NULLCHAR;
5961         bookRequested = FALSE;
5962         /* Program may be pondering now */
5963         cps->maybeThinking = TRUE;
5964         if (cps->sendTime == 2) cps->sendTime = 1;
5965         if (cps->offeredDraw) cps->offeredDraw--;
5966
5967 #if ZIPPY
5968         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5969             first.initDone) {
5970           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5971           ics_user_moved = 1;
5972           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5973                 char buf[3*MSG_SIZ];
5974
5975                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5976                         programStats.score / 100.,
5977                         programStats.depth,
5978                         programStats.time / 100.,
5979                         (unsigned int)programStats.nodes,
5980                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5981                         programStats.movelist);
5982                 SendToICS(buf);
5983 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5984           }
5985         }
5986 #endif
5987         /* currentMoveString is set as a side-effect of ParseOneMove */
5988         strcpy(machineMove, currentMoveString);
5989         strcat(machineMove, "\n");
5990         strcpy(moveList[forwardMostMove], machineMove);
5991
5992         /* [AS] Save move info and clear stats for next move */
5993         pvInfoList[ forwardMostMove ].score = programStats.score;
5994         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5995         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5996         ClearProgramStats();
5997         thinkOutput[0] = NULLCHAR;
5998         hiddenThinkOutputState = 0;
5999
6000         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6001
6002         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6003         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6004             int count = 0;
6005
6006             while( count < adjudicateLossPlies ) {
6007                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6008
6009                 if( count & 1 ) {
6010                     score = -score; /* Flip score for winning side */
6011                 }
6012
6013                 if( score > adjudicateLossThreshold ) {
6014                     break;
6015                 }
6016
6017                 count++;
6018             }
6019
6020             if( count >= adjudicateLossPlies ) {
6021                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6022
6023                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6024                     "Xboard adjudication", 
6025                     GE_XBOARD );
6026
6027                 return;
6028             }
6029         }
6030
6031         if( gameMode == TwoMachinesPlay ) {
6032           // [HGM] some adjudications useful with buggy engines
6033             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6034           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6035
6036
6037             if( appData.testLegality )
6038             {   /* [HGM] Some more adjudications for obstinate engines */
6039                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6040                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6041                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6042                 static int moveCount = 6;
6043                 ChessMove result;
6044                 char *reason = NULL;
6045
6046                 /* Count what is on board. */
6047                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6048                 {   ChessSquare p = boards[forwardMostMove][i][j];
6049                     int m=i;
6050
6051                     switch((int) p)
6052                     {   /* count B,N,R and other of each side */
6053                         case WhiteKing:
6054                         case BlackKing:
6055                              NrK++; break; // [HGM] atomic: count Kings
6056                         case WhiteKnight:
6057                              NrWN++; break;
6058                         case WhiteBishop:
6059                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6060                              bishopsColor |= 1 << ((i^j)&1);
6061                              NrWB++; break;
6062                         case BlackKnight:
6063                              NrBN++; break;
6064                         case BlackBishop:
6065                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6066                              bishopsColor |= 1 << ((i^j)&1);
6067                              NrBB++; break;
6068                         case WhiteRook:
6069                              NrWR++; break;
6070                         case BlackRook:
6071                              NrBR++; break;
6072                         case WhiteQueen:
6073                              NrWQ++; break;
6074                         case BlackQueen:
6075                              NrBQ++; break;
6076                         case EmptySquare: 
6077                              break;
6078                         case BlackPawn:
6079                              m = 7-i;
6080                         case WhitePawn:
6081                              PawnAdvance += m; NrPawns++;
6082                     }
6083                     NrPieces += (p != EmptySquare);
6084                     NrW += ((int)p < (int)BlackPawn);
6085                     if(gameInfo.variant == VariantXiangqi && 
6086                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6087                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6088                         NrW -= ((int)p < (int)BlackPawn);
6089                     }
6090                 }
6091
6092                 /* Some material-based adjudications that have to be made before stalemate test */
6093                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6094                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6095                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6096                      if(appData.checkMates) {
6097                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6098                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6100                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6101                          return;
6102                      }
6103                 }
6104
6105                 /* Bare King in Shatranj (loses) or Losers (wins) */
6106                 if( NrW == 1 || NrPieces - NrW == 1) {
6107                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6108                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6109                      if(appData.checkMates) {
6110                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6111                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6113                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6114                          return;
6115                      }
6116                   } else
6117                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6118                   {    /* bare King */
6119                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6120                         if(appData.checkMates) {
6121                             /* but only adjudicate if adjudication enabled */
6122                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6123                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6124                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6125                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6126                             return;
6127                         }
6128                   }
6129                 } else bare = 1;
6130
6131
6132             // don't wait for engine to announce game end if we can judge ourselves
6133             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6134                                        castlingRights[forwardMostMove]) ) {
6135               case MT_CHECK:
6136                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6137                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6138                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6139                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6140                             checkCnt++;
6141                         if(checkCnt >= 2) {
6142                             reason = "Xboard adjudication: 3rd check";
6143                             epStatus[forwardMostMove] = EP_CHECKMATE;
6144                             break;
6145                         }
6146                     }
6147                 }
6148               case MT_NONE:
6149               default:
6150                 break;
6151               case MT_STALEMATE:
6152               case MT_STAINMATE:
6153                 reason = "Xboard adjudication: Stalemate";
6154                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6155                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6156                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6157                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6158                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6159                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6160                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6161                                                                         EP_CHECKMATE : EP_WINS);
6162                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6163                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6164                 }
6165                 break;
6166               case MT_CHECKMATE:
6167                 reason = "Xboard adjudication: Checkmate";
6168                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6169                 break;
6170             }
6171
6172                 switch(i = epStatus[forwardMostMove]) {
6173                     case EP_STALEMATE:
6174                         result = GameIsDrawn; break;
6175                     case EP_CHECKMATE:
6176                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6177                     case EP_WINS:
6178                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6179                     default:
6180                         result = (ChessMove) 0;
6181                 }
6182                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6183                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6184                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6185                     GameEnds( result, reason, GE_XBOARD );
6186                     return;
6187                 }
6188
6189                 /* Next absolutely insufficient mating material. */
6190                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6191                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6192                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6193                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6194                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6195
6196                      /* always flag draws, for judging claims */
6197                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6198
6199                      if(appData.materialDraws) {
6200                          /* but only adjudicate them if adjudication enabled */
6201                          SendToProgram("force\n", cps->other); // suppress reply
6202                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6203                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6204                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6205                          return;
6206                      }
6207                 }
6208
6209                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6210                 if(NrPieces == 4 && 
6211                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6212                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6213                    || NrWN==2 || NrBN==2     /* KNNK */
6214                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6215                   ) ) {
6216                      if(--moveCount < 0 && appData.trivialDraws)
6217                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6218                           SendToProgram("force\n", cps->other); // suppress reply
6219                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6220                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6221                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6222                           return;
6223                      }
6224                 } else moveCount = 6;
6225             }
6226           }
6227
6228                 /* Check for rep-draws */
6229                 count = 0;
6230                 for(k = forwardMostMove-2;
6231                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6232                         epStatus[k] < EP_UNKNOWN &&
6233                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6234                     k-=2)
6235                 {   int rights=0;
6236                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6237                         /* compare castling rights */
6238                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6239                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6240                                 rights++; /* King lost rights, while rook still had them */
6241                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6242                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6243                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6244                                    rights++; /* but at least one rook lost them */
6245                         }
6246                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6247                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6248                                 rights++; 
6249                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6250                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6251                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6252                                    rights++;
6253                         }
6254                         if( rights == 0 && ++count > appData.drawRepeats-2
6255                             && appData.drawRepeats > 1) {
6256                              /* adjudicate after user-specified nr of repeats */
6257                              SendToProgram("force\n", cps->other); // suppress reply
6258                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6259                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6260                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6261                                 // [HGM] xiangqi: check for forbidden perpetuals
6262                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6263                                 for(m=forwardMostMove; m>k; m-=2) {
6264                                     if(MateTest(boards[m], PosFlags(m), 
6265                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6266                                         ourPerpetual = 0; // the current mover did not always check
6267                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6268                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6269                                         hisPerpetual = 0; // the opponent did not always check
6270                                 }
6271                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6272                                                                         ourPerpetual, hisPerpetual);
6273                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6274                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6275                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6276                                     return;
6277                                 }
6278                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6279                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6280                                 // Now check for perpetual chases
6281                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6282                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6283                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6284                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6285                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6286                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6287                                         return;
6288                                     }
6289                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6290                                         break; // Abort repetition-checking loop.
6291                                 }
6292                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6293                              }
6294                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6295                              return;
6296                         }
6297                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6298                              epStatus[forwardMostMove] = EP_REP_DRAW;
6299                     }
6300                 }
6301
6302                 /* Now we test for 50-move draws. Determine ply count */
6303                 count = forwardMostMove;
6304                 /* look for last irreversble move */
6305                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6306                     count--;
6307                 /* if we hit starting position, add initial plies */
6308                 if( count == backwardMostMove )
6309                     count -= initialRulePlies;
6310                 count = forwardMostMove - count; 
6311                 if( count >= 100)
6312                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6313                          /* this is used to judge if draw claims are legal */
6314                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6315                          SendToProgram("force\n", cps->other); // suppress reply
6316                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6317                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6319                          return;
6320                 }
6321
6322                 /* if draw offer is pending, treat it as a draw claim
6323                  * when draw condition present, to allow engines a way to
6324                  * claim draws before making their move to avoid a race
6325                  * condition occurring after their move
6326                  */
6327                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6328                          char *p = NULL;
6329                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6330                              p = "Draw claim: 50-move rule";
6331                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6332                              p = "Draw claim: 3-fold repetition";
6333                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6334                              p = "Draw claim: insufficient mating material";
6335                          if( p != NULL ) {
6336                              SendToProgram("force\n", cps->other); // suppress reply
6337                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6338                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6339                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6340                              return;
6341                          }
6342                 }
6343
6344
6345                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6346                     SendToProgram("force\n", cps->other); // suppress reply
6347                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6348                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6349
6350                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6351
6352                     return;
6353                 }
6354         }
6355
6356         bookHit = NULL;
6357         if (gameMode == TwoMachinesPlay) {
6358             /* [HGM] relaying draw offers moved to after reception of move */
6359             /* and interpreting offer as claim if it brings draw condition */
6360             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6361                 SendToProgram("draw\n", cps->other);
6362             }
6363             if (cps->other->sendTime) {
6364                 SendTimeRemaining(cps->other,
6365                                   cps->other->twoMachinesColor[0] == 'w');
6366             }
6367             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6368             if (firstMove && !bookHit) {
6369                 firstMove = FALSE;
6370                 if (cps->other->useColors) {
6371                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6372                 }
6373                 SendToProgram("go\n", cps->other);
6374             }
6375             cps->other->maybeThinking = TRUE;
6376         }
6377
6378         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379         
6380         if (!pausing && appData.ringBellAfterMoves) {
6381             RingBell();
6382         }
6383
6384         /* 
6385          * Reenable menu items that were disabled while
6386          * machine was thinking
6387          */
6388         if (gameMode != TwoMachinesPlay)
6389             SetUserThinkingEnables();
6390
6391         // [HGM] book: after book hit opponent has received move and is now in force mode
6392         // force the book reply into it, and then fake that it outputted this move by jumping
6393         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6394         if(bookHit) {
6395                 static char bookMove[MSG_SIZ]; // a bit generous?
6396
6397                 strcpy(bookMove, "move ");
6398                 strcat(bookMove, bookHit);
6399                 message = bookMove;
6400                 cps = cps->other;
6401                 programStats.nodes = programStats.depth = programStats.time = 
6402                 programStats.score = programStats.got_only_move = 0;
6403                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6404
6405                 if(cps->lastPing != cps->lastPong) {
6406                     savedMessage = message; // args for deferred call
6407                     savedState = cps;
6408                     ScheduleDelayedEvent(DeferredBookMove, 10);
6409                     return;
6410                 }
6411                 goto FakeBookMove;
6412         }
6413
6414         return;
6415     }
6416
6417     /* Set special modes for chess engines.  Later something general
6418      *  could be added here; for now there is just one kludge feature,
6419      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6420      *  when "xboard" is given as an interactive command.
6421      */
6422     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6423         cps->useSigint = FALSE;
6424         cps->useSigterm = FALSE;
6425     }
6426     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6427       ParseFeatures(message+8, cps);
6428       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6429     }
6430
6431     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6432      * want this, I was asked to put it in, and obliged.
6433      */
6434     if (!strncmp(message, "setboard ", 9)) {
6435         Board initial_position; int i;
6436
6437         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6438
6439         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6440             DisplayError(_("Bad FEN received from engine"), 0);
6441             return ;
6442         } else {
6443            Reset(TRUE, FALSE);
6444            CopyBoard(boards[0], initial_position);
6445            initialRulePlies = FENrulePlies;
6446            epStatus[0] = FENepStatus;
6447            for( i=0; i<nrCastlingRights; i++ )
6448                 castlingRights[0][i] = FENcastlingRights[i];
6449            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6450            else gameMode = MachinePlaysBlack;                 
6451            DrawPosition(FALSE, boards[currentMove]);
6452         }
6453         return;
6454     }
6455
6456     /*
6457      * Look for communication commands
6458      */
6459     if (!strncmp(message, "telluser ", 9)) {
6460         DisplayNote(message + 9);
6461         return;
6462     }
6463     if (!strncmp(message, "tellusererror ", 14)) {
6464         DisplayError(message + 14, 0);
6465         return;
6466     }
6467     if (!strncmp(message, "tellopponent ", 13)) {
6468       if (appData.icsActive) {
6469         if (loggedOn) {
6470           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6471           SendToICS(buf1);
6472         }
6473       } else {
6474         DisplayNote(message + 13);
6475       }
6476       return;
6477     }
6478     if (!strncmp(message, "tellothers ", 11)) {
6479       if (appData.icsActive) {
6480         if (loggedOn) {
6481           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6482           SendToICS(buf1);
6483         }
6484       }
6485       return;
6486     }
6487     if (!strncmp(message, "tellall ", 8)) {
6488       if (appData.icsActive) {
6489         if (loggedOn) {
6490           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6491           SendToICS(buf1);
6492         }
6493       } else {
6494         DisplayNote(message + 8);
6495       }
6496       return;
6497     }
6498     if (strncmp(message, "warning", 7) == 0) {
6499         /* Undocumented feature, use tellusererror in new code */
6500         DisplayError(message, 0);
6501         return;
6502     }
6503     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6504         strcpy(realname, cps->tidy);
6505         strcat(realname, " query");
6506         AskQuestion(realname, buf2, buf1, cps->pr);
6507         return;
6508     }
6509     /* Commands from the engine directly to ICS.  We don't allow these to be 
6510      *  sent until we are logged on. Crafty kibitzes have been known to 
6511      *  interfere with the login process.
6512      */
6513     if (loggedOn) {
6514         if (!strncmp(message, "tellics ", 8)) {
6515             SendToICS(message + 8);
6516             SendToICS("\n");
6517             return;
6518         }
6519         if (!strncmp(message, "tellicsnoalias ", 15)) {
6520             SendToICS(ics_prefix);
6521             SendToICS(message + 15);
6522             SendToICS("\n");
6523             return;
6524         }
6525         /* The following are for backward compatibility only */
6526         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6527             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6528             SendToICS(ics_prefix);
6529             SendToICS(message);
6530             SendToICS("\n");
6531             return;
6532         }
6533     }
6534     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6535         return;
6536     }
6537     /*
6538      * If the move is illegal, cancel it and redraw the board.
6539      * Also deal with other error cases.  Matching is rather loose
6540      * here to accommodate engines written before the spec.
6541      */
6542     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6543         strncmp(message, "Error", 5) == 0) {
6544         if (StrStr(message, "name") || 
6545             StrStr(message, "rating") || StrStr(message, "?") ||
6546             StrStr(message, "result") || StrStr(message, "board") ||
6547             StrStr(message, "bk") || StrStr(message, "computer") ||
6548             StrStr(message, "variant") || StrStr(message, "hint") ||
6549             StrStr(message, "random") || StrStr(message, "depth") ||
6550             StrStr(message, "accepted")) {
6551             return;
6552         }
6553         if (StrStr(message, "protover")) {
6554           /* Program is responding to input, so it's apparently done
6555              initializing, and this error message indicates it is
6556              protocol version 1.  So we don't need to wait any longer
6557              for it to initialize and send feature commands. */
6558           FeatureDone(cps, 1);
6559           cps->protocolVersion = 1;
6560           return;
6561         }
6562         cps->maybeThinking = FALSE;
6563
6564         if (StrStr(message, "draw")) {
6565             /* Program doesn't have "draw" command */
6566             cps->sendDrawOffers = 0;
6567             return;
6568         }
6569         if (cps->sendTime != 1 &&
6570             (StrStr(message, "time") || StrStr(message, "otim"))) {
6571           /* Program apparently doesn't have "time" or "otim" command */
6572           cps->sendTime = 0;
6573           return;
6574         }
6575         if (StrStr(message, "analyze")) {
6576             cps->analysisSupport = FALSE;
6577             cps->analyzing = FALSE;
6578             Reset(FALSE, TRUE);
6579             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6580             DisplayError(buf2, 0);
6581             return;
6582         }
6583         if (StrStr(message, "(no matching move)st")) {
6584           /* Special kludge for GNU Chess 4 only */
6585           cps->stKludge = TRUE;
6586           SendTimeControl(cps, movesPerSession, timeControl,
6587                           timeIncrement, appData.searchDepth,
6588                           searchTime);
6589           return;
6590         }
6591         if (StrStr(message, "(no matching move)sd")) {
6592           /* Special kludge for GNU Chess 4 only */
6593           cps->sdKludge = TRUE;
6594           SendTimeControl(cps, movesPerSession, timeControl,
6595                           timeIncrement, appData.searchDepth,
6596                           searchTime);
6597           return;
6598         }
6599         if (!StrStr(message, "llegal")) {
6600             return;
6601         }
6602         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6603             gameMode == IcsIdle) return;
6604         if (forwardMostMove <= backwardMostMove) return;
6605         if (pausing) PauseEvent();
6606       if(appData.forceIllegal) {
6607             // [HGM] illegal: machine refused move; force position after move into it
6608           SendToProgram("force\n", cps);
6609           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6610                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6611                 // when black is to move, while there might be nothing on a2 or black
6612                 // might already have the move. So send the board as if white has the move.
6613                 // But first we must change the stm of the engine, as it refused the last move
6614                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6615                 if(WhiteOnMove(forwardMostMove)) {
6616                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6617                     SendBoard(cps, forwardMostMove); // kludgeless board
6618                 } else {
6619                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6620                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6621                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6622                 }
6623           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6624             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6625                  gameMode == TwoMachinesPlay)
6626               SendToProgram("go\n", cps);
6627             return;
6628       } else
6629         if (gameMode == PlayFromGameFile) {
6630             /* Stop reading this game file */
6631             gameMode = EditGame;
6632             ModeHighlight();
6633         }
6634         currentMove = --forwardMostMove;
6635         DisplayMove(currentMove-1); /* before DisplayMoveError */
6636         SwitchClocks();
6637         DisplayBothClocks();
6638         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6639                 parseList[currentMove], cps->which);
6640         DisplayMoveError(buf1);
6641         DrawPosition(FALSE, boards[currentMove]);
6642
6643         /* [HGM] illegal-move claim should forfeit game when Xboard */
6644         /* only passes fully legal moves                            */
6645         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6646             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6647                                 "False illegal-move claim", GE_XBOARD );
6648         }
6649         return;
6650     }
6651     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6652         /* Program has a broken "time" command that
6653            outputs a string not ending in newline.
6654            Don't use it. */
6655         cps->sendTime = 0;
6656     }
6657     
6658     /*
6659      * If chess program startup fails, exit with an error message.
6660      * Attempts to recover here are futile.
6661      */
6662     if ((StrStr(message, "unknown host") != NULL)
6663         || (StrStr(message, "No remote directory") != NULL)
6664         || (StrStr(message, "not found") != NULL)
6665         || (StrStr(message, "No such file") != NULL)
6666         || (StrStr(message, "can't alloc") != NULL)
6667         || (StrStr(message, "Permission denied") != NULL)) {
6668
6669         cps->maybeThinking = FALSE;
6670         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6671                 cps->which, cps->program, cps->host, message);
6672         RemoveInputSource(cps->isr);
6673         DisplayFatalError(buf1, 0, 1);
6674         return;
6675     }
6676     
6677     /* 
6678      * Look for hint output
6679      */
6680     if (sscanf(message, "Hint: %s", buf1) == 1) {
6681         if (cps == &first && hintRequested) {
6682             hintRequested = FALSE;
6683             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6684                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6685                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6686                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6687                                     fromY, fromX, toY, toX, promoChar, buf1);
6688                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6689                 DisplayInformation(buf2);
6690             } else {
6691                 /* Hint move could not be parsed!? */
6692               snprintf(buf2, sizeof(buf2),
6693                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6694                         buf1, cps->which);
6695                 DisplayError(buf2, 0);
6696             }
6697         } else {
6698             strcpy(lastHint, buf1);
6699         }
6700         return;
6701     }
6702
6703     /*
6704      * Ignore other messages if game is not in progress
6705      */
6706     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6707         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6708
6709     /*
6710      * look for win, lose, draw, or draw offer
6711      */
6712     if (strncmp(message, "1-0", 3) == 0) {
6713         char *p, *q, *r = "";
6714         p = strchr(message, '{');
6715         if (p) {
6716             q = strchr(p, '}');
6717             if (q) {
6718                 *q = NULLCHAR;
6719                 r = p + 1;
6720             }
6721         }
6722         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6723         return;
6724     } else if (strncmp(message, "0-1", 3) == 0) {
6725         char *p, *q, *r = "";
6726         p = strchr(message, '{');
6727         if (p) {
6728             q = strchr(p, '}');
6729             if (q) {
6730                 *q = NULLCHAR;
6731                 r = p + 1;
6732             }
6733         }
6734         /* Kludge for Arasan 4.1 bug */
6735         if (strcmp(r, "Black resigns") == 0) {
6736             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6737             return;
6738         }
6739         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6740         return;
6741     } else if (strncmp(message, "1/2", 3) == 0) {
6742         char *p, *q, *r = "";
6743         p = strchr(message, '{');
6744         if (p) {
6745             q = strchr(p, '}');
6746             if (q) {
6747                 *q = NULLCHAR;
6748                 r = p + 1;
6749             }
6750         }
6751             
6752         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6753         return;
6754
6755     } else if (strncmp(message, "White resign", 12) == 0) {
6756         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6757         return;
6758     } else if (strncmp(message, "Black resign", 12) == 0) {
6759         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6760         return;
6761     } else if (strncmp(message, "White matches", 13) == 0 ||
6762                strncmp(message, "Black matches", 13) == 0   ) {
6763         /* [HGM] ignore GNUShogi noises */
6764         return;
6765     } else if (strncmp(message, "White", 5) == 0 &&
6766                message[5] != '(' &&
6767                StrStr(message, "Black") == NULL) {
6768         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6769         return;
6770     } else if (strncmp(message, "Black", 5) == 0 &&
6771                message[5] != '(') {
6772         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6773         return;
6774     } else if (strcmp(message, "resign") == 0 ||
6775                strcmp(message, "computer resigns") == 0) {
6776         switch (gameMode) {
6777           case MachinePlaysBlack:
6778           case IcsPlayingBlack:
6779             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6780             break;
6781           case MachinePlaysWhite:
6782           case IcsPlayingWhite:
6783             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6784             break;
6785           case TwoMachinesPlay:
6786             if (cps->twoMachinesColor[0] == 'w')
6787               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6788             else
6789               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6790             break;
6791           default:
6792             /* can't happen */
6793             break;
6794         }
6795         return;
6796     } else if (strncmp(message, "opponent mates", 14) == 0) {
6797         switch (gameMode) {
6798           case MachinePlaysBlack:
6799           case IcsPlayingBlack:
6800             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6801             break;
6802           case MachinePlaysWhite:
6803           case IcsPlayingWhite:
6804             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6805             break;
6806           case TwoMachinesPlay:
6807             if (cps->twoMachinesColor[0] == 'w')
6808               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6809             else
6810               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6811             break;
6812           default:
6813             /* can't happen */
6814             break;
6815         }
6816         return;
6817     } else if (strncmp(message, "computer mates", 14) == 0) {
6818         switch (gameMode) {
6819           case MachinePlaysBlack:
6820           case IcsPlayingBlack:
6821             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6822             break;
6823           case MachinePlaysWhite:
6824           case IcsPlayingWhite:
6825             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6826             break;
6827           case TwoMachinesPlay:
6828             if (cps->twoMachinesColor[0] == 'w')
6829               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6830             else
6831               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6832             break;
6833           default:
6834             /* can't happen */
6835             break;
6836         }
6837         return;
6838     } else if (strncmp(message, "checkmate", 9) == 0) {
6839         if (WhiteOnMove(forwardMostMove)) {
6840             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6841         } else {
6842             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6843         }
6844         return;
6845     } else if (strstr(message, "Draw") != NULL ||
6846                strstr(message, "game is a draw") != NULL) {
6847         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6848         return;
6849     } else if (strstr(message, "offer") != NULL &&
6850                strstr(message, "draw") != NULL) {
6851 #if ZIPPY
6852         if (appData.zippyPlay && first.initDone) {
6853             /* Relay offer to ICS */
6854             SendToICS(ics_prefix);
6855             SendToICS("draw\n");
6856         }
6857 #endif
6858         cps->offeredDraw = 2; /* valid until this engine moves twice */
6859         if (gameMode == TwoMachinesPlay) {
6860             if (cps->other->offeredDraw) {
6861                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6862             /* [HGM] in two-machine mode we delay relaying draw offer      */
6863             /* until after we also have move, to see if it is really claim */
6864             }
6865         } else if (gameMode == MachinePlaysWhite ||
6866                    gameMode == MachinePlaysBlack) {
6867           if (userOfferedDraw) {
6868             DisplayInformation(_("Machine accepts your draw offer"));
6869             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6870           } else {
6871             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6872           }
6873         }
6874     }
6875
6876     
6877     /*
6878      * Look for thinking output
6879      */
6880     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6881           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6882                                 ) {
6883         int plylev, mvleft, mvtot, curscore, time;
6884         char mvname[MOVE_LEN];
6885         u64 nodes; // [DM]
6886         char plyext;
6887         int ignore = FALSE;
6888         int prefixHint = FALSE;
6889         mvname[0] = NULLCHAR;
6890
6891         switch (gameMode) {
6892           case MachinePlaysBlack:
6893           case IcsPlayingBlack:
6894             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6895             break;
6896           case MachinePlaysWhite:
6897           case IcsPlayingWhite:
6898             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6899             break;
6900           case AnalyzeMode:
6901           case AnalyzeFile:
6902             break;
6903           case IcsObserving: /* [DM] icsEngineAnalyze */
6904             if (!appData.icsEngineAnalyze) ignore = TRUE;
6905             break;
6906           case TwoMachinesPlay:
6907             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6908                 ignore = TRUE;
6909             }
6910             break;
6911           default:
6912             ignore = TRUE;
6913             break;
6914         }
6915
6916         if (!ignore) {
6917             buf1[0] = NULLCHAR;
6918             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6919                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6920
6921                 if (plyext != ' ' && plyext != '\t') {
6922                     time *= 100;
6923                 }
6924
6925                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6926                 if( cps->scoreIsAbsolute && 
6927                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6928                 {
6929                     curscore = -curscore;
6930                 }
6931
6932
6933                 programStats.depth = plylev;
6934                 programStats.nodes = nodes;
6935                 programStats.time = time;
6936                 programStats.score = curscore;
6937                 programStats.got_only_move = 0;
6938
6939                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6940                         int ticklen;
6941
6942                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6943                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6944                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6945                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6946                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6947                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6948                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6949                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6950                 }
6951
6952                 /* Buffer overflow protection */
6953                 if (buf1[0] != NULLCHAR) {
6954                     if (strlen(buf1) >= sizeof(programStats.movelist)
6955                         && appData.debugMode) {
6956                         fprintf(debugFP,
6957                                 "PV is too long; using the first %u bytes.\n",
6958                                 (unsigned) sizeof(programStats.movelist) - 1);
6959                     }
6960
6961                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6962                 } else {
6963                     sprintf(programStats.movelist, " no PV\n");
6964                 }
6965
6966                 if (programStats.seen_stat) {
6967                     programStats.ok_to_send = 1;
6968                 }
6969
6970                 if (strchr(programStats.movelist, '(') != NULL) {
6971                     programStats.line_is_book = 1;
6972                     programStats.nr_moves = 0;
6973                     programStats.moves_left = 0;
6974                 } else {
6975                     programStats.line_is_book = 0;
6976                 }
6977
6978                 SendProgramStatsToFrontend( cps, &programStats );
6979
6980                 /* 
6981                     [AS] Protect the thinkOutput buffer from overflow... this
6982                     is only useful if buf1 hasn't overflowed first!
6983                 */
6984                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6985                         plylev, 
6986                         (gameMode == TwoMachinesPlay ?
6987                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6988                         ((double) curscore) / 100.0,
6989                         prefixHint ? lastHint : "",
6990                         prefixHint ? " " : "" );
6991
6992                 if( buf1[0] != NULLCHAR ) {
6993                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6994
6995                     if( strlen(buf1) > max_len ) {
6996                         if( appData.debugMode) {
6997                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6998                         }
6999                         buf1[max_len+1] = '\0';
7000                     }
7001
7002                     strcat( thinkOutput, buf1 );
7003                 }
7004
7005                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7006                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7007                     DisplayMove(currentMove - 1);
7008                 }
7009                 return;
7010
7011             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7012                 /* crafty (9.25+) says "(only move) <move>"
7013                  * if there is only 1 legal move
7014                  */
7015                 sscanf(p, "(only move) %s", buf1);
7016                 sprintf(thinkOutput, "%s (only move)", buf1);
7017                 sprintf(programStats.movelist, "%s (only move)", buf1);
7018                 programStats.depth = 1;
7019                 programStats.nr_moves = 1;
7020                 programStats.moves_left = 1;
7021                 programStats.nodes = 1;
7022                 programStats.time = 1;
7023                 programStats.got_only_move = 1;
7024
7025                 /* Not really, but we also use this member to
7026                    mean "line isn't going to change" (Crafty
7027                    isn't searching, so stats won't change) */
7028                 programStats.line_is_book = 1;
7029
7030                 SendProgramStatsToFrontend( cps, &programStats );
7031                 
7032                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7033                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7034                     DisplayMove(currentMove - 1);
7035                 }
7036                 return;
7037             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7038                               &time, &nodes, &plylev, &mvleft,
7039                               &mvtot, mvname) >= 5) {
7040                 /* The stat01: line is from Crafty (9.29+) in response
7041                    to the "." command */
7042                 programStats.seen_stat = 1;
7043                 cps->maybeThinking = TRUE;
7044
7045                 if (programStats.got_only_move || !appData.periodicUpdates)
7046                   return;
7047
7048                 programStats.depth = plylev;
7049                 programStats.time = time;
7050                 programStats.nodes = nodes;
7051                 programStats.moves_left = mvleft;
7052                 programStats.nr_moves = mvtot;
7053                 strcpy(programStats.move_name, mvname);
7054                 programStats.ok_to_send = 1;
7055                 programStats.movelist[0] = '\0';
7056
7057                 SendProgramStatsToFrontend( cps, &programStats );
7058
7059                 return;
7060
7061             } else if (strncmp(message,"++",2) == 0) {
7062                 /* Crafty 9.29+ outputs this */
7063                 programStats.got_fail = 2;
7064                 return;
7065
7066             } else if (strncmp(message,"--",2) == 0) {
7067                 /* Crafty 9.29+ outputs this */
7068                 programStats.got_fail = 1;
7069                 return;
7070
7071             } else if (thinkOutput[0] != NULLCHAR &&
7072                        strncmp(message, "    ", 4) == 0) {
7073                 unsigned message_len;
7074
7075                 p = message;
7076                 while (*p && *p == ' ') p++;
7077
7078                 message_len = strlen( p );
7079
7080                 /* [AS] Avoid buffer overflow */
7081                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7082                     strcat(thinkOutput, " ");
7083                     strcat(thinkOutput, p);
7084                 }
7085
7086                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7087                     strcat(programStats.movelist, " ");
7088                     strcat(programStats.movelist, p);
7089                 }
7090
7091                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7092                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7093                     DisplayMove(currentMove - 1);
7094                 }
7095                 return;
7096             }
7097         }
7098         else {
7099             buf1[0] = NULLCHAR;
7100
7101             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7102                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7103             {
7104                 ChessProgramStats cpstats;
7105
7106                 if (plyext != ' ' && plyext != '\t') {
7107                     time *= 100;
7108                 }
7109
7110                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7111                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7112                     curscore = -curscore;
7113                 }
7114
7115                 cpstats.depth = plylev;
7116                 cpstats.nodes = nodes;
7117                 cpstats.time = time;
7118                 cpstats.score = curscore;
7119                 cpstats.got_only_move = 0;
7120                 cpstats.movelist[0] = '\0';
7121
7122                 if (buf1[0] != NULLCHAR) {
7123                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7124                 }
7125
7126                 cpstats.ok_to_send = 0;
7127                 cpstats.line_is_book = 0;
7128                 cpstats.nr_moves = 0;
7129                 cpstats.moves_left = 0;
7130
7131                 SendProgramStatsToFrontend( cps, &cpstats );
7132             }
7133         }
7134     }
7135 }
7136
7137
7138 /* Parse a game score from the character string "game", and
7139    record it as the history of the current game.  The game
7140    score is NOT assumed to start from the standard position. 
7141    The display is not updated in any way.
7142    */
7143 void
7144 ParseGameHistory(game)
7145      char *game;
7146 {
7147     ChessMove moveType;
7148     int fromX, fromY, toX, toY, boardIndex;
7149     char promoChar;
7150     char *p, *q;
7151     char buf[MSG_SIZ];
7152
7153     if (appData.debugMode)
7154       fprintf(debugFP, "Parsing game history: %s\n", game);
7155
7156     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7157     gameInfo.site = StrSave(appData.icsHost);
7158     gameInfo.date = PGNDate();
7159     gameInfo.round = StrSave("-");
7160
7161     /* Parse out names of players */
7162     while (*game == ' ') game++;
7163     p = buf;
7164     while (*game != ' ') *p++ = *game++;
7165     *p = NULLCHAR;
7166     gameInfo.white = StrSave(buf);
7167     while (*game == ' ') game++;
7168     p = buf;
7169     while (*game != ' ' && *game != '\n') *p++ = *game++;
7170     *p = NULLCHAR;
7171     gameInfo.black = StrSave(buf);
7172
7173     /* Parse moves */
7174     boardIndex = blackPlaysFirst ? 1 : 0;
7175     yynewstr(game);
7176     for (;;) {
7177         yyboardindex = boardIndex;
7178         moveType = (ChessMove) yylex();
7179         switch (moveType) {
7180           case IllegalMove:             /* maybe suicide chess, etc. */
7181   if (appData.debugMode) {
7182     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7183     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7184     setbuf(debugFP, NULL);
7185   }
7186           case WhitePromotionChancellor:
7187           case BlackPromotionChancellor:
7188           case WhitePromotionArchbishop:
7189           case BlackPromotionArchbishop:
7190           case WhitePromotionQueen:
7191           case BlackPromotionQueen:
7192           case WhitePromotionRook:
7193           case BlackPromotionRook:
7194           case WhitePromotionBishop:
7195           case BlackPromotionBishop:
7196           case WhitePromotionKnight:
7197           case BlackPromotionKnight:
7198           case WhitePromotionKing:
7199           case BlackPromotionKing:
7200           case NormalMove:
7201           case WhiteCapturesEnPassant:
7202           case BlackCapturesEnPassant:
7203           case WhiteKingSideCastle:
7204           case WhiteQueenSideCastle:
7205           case BlackKingSideCastle:
7206           case BlackQueenSideCastle:
7207           case WhiteKingSideCastleWild:
7208           case WhiteQueenSideCastleWild:
7209           case BlackKingSideCastleWild:
7210           case BlackQueenSideCastleWild:
7211           /* PUSH Fabien */
7212           case WhiteHSideCastleFR:
7213           case WhiteASideCastleFR:
7214           case BlackHSideCastleFR:
7215           case BlackASideCastleFR:
7216           /* POP Fabien */
7217             fromX = currentMoveString[0] - AAA;
7218             fromY = currentMoveString[1] - ONE;
7219             toX = currentMoveString[2] - AAA;
7220             toY = currentMoveString[3] - ONE;
7221             promoChar = currentMoveString[4];
7222             break;
7223           case WhiteDrop:
7224           case BlackDrop:
7225             fromX = moveType == WhiteDrop ?
7226               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7227             (int) CharToPiece(ToLower(currentMoveString[0]));
7228             fromY = DROP_RANK;
7229             toX = currentMoveString[2] - AAA;
7230             toY = currentMoveString[3] - ONE;
7231             promoChar = NULLCHAR;
7232             break;
7233           case AmbiguousMove:
7234             /* bug? */
7235             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7236   if (appData.debugMode) {
7237     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7238     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7239     setbuf(debugFP, NULL);
7240   }
7241             DisplayError(buf, 0);
7242             return;
7243           case ImpossibleMove:
7244             /* bug? */
7245             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7246   if (appData.debugMode) {
7247     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7248     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7249     setbuf(debugFP, NULL);
7250   }
7251             DisplayError(buf, 0);
7252             return;
7253           case (ChessMove) 0:   /* end of file */
7254             if (boardIndex < backwardMostMove) {
7255                 /* Oops, gap.  How did that happen? */
7256                 DisplayError(_("Gap in move list"), 0);
7257                 return;
7258             }
7259             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7260             if (boardIndex > forwardMostMove) {
7261                 forwardMostMove = boardIndex;
7262             }
7263             return;
7264           case ElapsedTime:
7265             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7266                 strcat(parseList[boardIndex-1], " ");
7267                 strcat(parseList[boardIndex-1], yy_text);
7268             }
7269             continue;
7270           case Comment:
7271           case PGNTag:
7272           case NAG:
7273           default:
7274             /* ignore */
7275             continue;
7276           case WhiteWins:
7277           case BlackWins:
7278           case GameIsDrawn:
7279           case GameUnfinished:
7280             if (gameMode == IcsExamining) {
7281                 if (boardIndex < backwardMostMove) {
7282                     /* Oops, gap.  How did that happen? */
7283                     return;
7284                 }
7285                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7286                 return;
7287             }
7288             gameInfo.result = moveType;
7289             p = strchr(yy_text, '{');
7290             if (p == NULL) p = strchr(yy_text, '(');
7291             if (p == NULL) {
7292                 p = yy_text;
7293                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7294             } else {
7295                 q = strchr(p, *p == '{' ? '}' : ')');
7296                 if (q != NULL) *q = NULLCHAR;
7297                 p++;
7298             }
7299             gameInfo.resultDetails = StrSave(p);
7300             continue;
7301         }
7302         if (boardIndex >= forwardMostMove &&
7303             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7304             backwardMostMove = blackPlaysFirst ? 1 : 0;
7305             return;
7306         }
7307         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7308                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7309                                  parseList[boardIndex]);
7310         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7311         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7312         /* currentMoveString is set as a side-effect of yylex */
7313         strcpy(moveList[boardIndex], currentMoveString);
7314         strcat(moveList[boardIndex], "\n");
7315         boardIndex++;
7316         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7317                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7318         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7319                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7320           case MT_NONE:
7321           case MT_STALEMATE:
7322           default:
7323             break;
7324           case MT_CHECK:
7325             if(gameInfo.variant != VariantShogi)
7326                 strcat(parseList[boardIndex - 1], "+");
7327             break;
7328           case MT_CHECKMATE:
7329           case MT_STAINMATE:
7330             strcat(parseList[boardIndex - 1], "#");
7331             break;
7332         }
7333     }
7334 }
7335
7336
7337 /* Apply a move to the given board  */
7338 void
7339 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7340      int fromX, fromY, toX, toY;
7341      int promoChar;
7342      Board board;
7343      char *castling;
7344      char *ep;
7345 {
7346   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7347
7348     /* [HGM] compute & store e.p. status and castling rights for new position */
7349     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7350     { int i;
7351
7352       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7353       oldEP = *ep;
7354       *ep = EP_NONE;
7355
7356       if( board[toY][toX] != EmptySquare ) 
7357            *ep = EP_CAPTURE;  
7358
7359       if( board[fromY][fromX] == WhitePawn ) {
7360            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7361                *ep = EP_PAWN_MOVE;
7362            if( toY-fromY==2) {
7363                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7364                         gameInfo.variant != VariantBerolina || toX < fromX)
7365                       *ep = toX | berolina;
7366                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7367                         gameInfo.variant != VariantBerolina || toX > fromX) 
7368                       *ep = toX;
7369            }
7370       } else 
7371       if( board[fromY][fromX] == BlackPawn ) {
7372            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7373                *ep = EP_PAWN_MOVE; 
7374            if( toY-fromY== -2) {
7375                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7376                         gameInfo.variant != VariantBerolina || toX < fromX)
7377                       *ep = toX | berolina;
7378                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7379                         gameInfo.variant != VariantBerolina || toX > fromX) 
7380                       *ep = toX;
7381            }
7382        }
7383
7384        for(i=0; i<nrCastlingRights; i++) {
7385            if(castling[i] == fromX && castlingRank[i] == fromY ||
7386               castling[i] == toX   && castlingRank[i] == toY   
7387              ) castling[i] = -1; // revoke for moved or captured piece
7388        }
7389
7390     }
7391
7392   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7393   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7394        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7395          
7396   if (fromX == toX && fromY == toY) return;
7397
7398   if (fromY == DROP_RANK) {
7399         /* must be first */
7400         piece = board[toY][toX] = (ChessSquare) fromX;
7401   } else {
7402      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7403      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7404      if(gameInfo.variant == VariantKnightmate)
7405          king += (int) WhiteUnicorn - (int) WhiteKing;
7406
7407     /* Code added by Tord: */
7408     /* FRC castling assumed when king captures friendly rook. */
7409     if (board[fromY][fromX] == WhiteKing &&
7410              board[toY][toX] == WhiteRook) {
7411       board[fromY][fromX] = EmptySquare;
7412       board[toY][toX] = EmptySquare;
7413       if(toX > fromX) {
7414         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7415       } else {
7416         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7417       }
7418     } else if (board[fromY][fromX] == BlackKing &&
7419                board[toY][toX] == BlackRook) {
7420       board[fromY][fromX] = EmptySquare;
7421       board[toY][toX] = EmptySquare;
7422       if(toX > fromX) {
7423         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7424       } else {
7425         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7426       }
7427     /* End of code added by Tord */
7428
7429     } else if (board[fromY][fromX] == king
7430         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7431         && toY == fromY && toX > fromX+1) {
7432         board[fromY][fromX] = EmptySquare;
7433         board[toY][toX] = king;
7434         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7435         board[fromY][BOARD_RGHT-1] = EmptySquare;
7436     } else if (board[fromY][fromX] == king
7437         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7438                && toY == fromY && toX < fromX-1) {
7439         board[fromY][fromX] = EmptySquare;
7440         board[toY][toX] = king;
7441         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7442         board[fromY][BOARD_LEFT] = EmptySquare;
7443     } else if (board[fromY][fromX] == WhitePawn
7444                && toY == BOARD_HEIGHT-1
7445                && gameInfo.variant != VariantXiangqi
7446                ) {
7447         /* white pawn promotion */
7448         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7449         if (board[toY][toX] == EmptySquare) {
7450             board[toY][toX] = WhiteQueen;
7451         }
7452         if(gameInfo.variant==VariantBughouse ||
7453            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7454             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7455         board[fromY][fromX] = EmptySquare;
7456     } else if ((fromY == BOARD_HEIGHT-4)
7457                && (toX != fromX)
7458                && gameInfo.variant != VariantXiangqi
7459                && gameInfo.variant != VariantBerolina
7460                && (board[fromY][fromX] == WhitePawn)
7461                && (board[toY][toX] == EmptySquare)) {
7462         board[fromY][fromX] = EmptySquare;
7463         board[toY][toX] = WhitePawn;
7464         captured = board[toY - 1][toX];
7465         board[toY - 1][toX] = EmptySquare;
7466     } else if ((fromY == BOARD_HEIGHT-4)
7467                && (toX == fromX)
7468                && gameInfo.variant == VariantBerolina
7469                && (board[fromY][fromX] == WhitePawn)
7470                && (board[toY][toX] == EmptySquare)) {
7471         board[fromY][fromX] = EmptySquare;
7472         board[toY][toX] = WhitePawn;
7473         if(oldEP & EP_BEROLIN_A) {
7474                 captured = board[fromY][fromX-1];
7475                 board[fromY][fromX-1] = EmptySquare;
7476         }else{  captured = board[fromY][fromX+1];
7477                 board[fromY][fromX+1] = EmptySquare;
7478         }
7479     } else if (board[fromY][fromX] == king
7480         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7481                && toY == fromY && toX > fromX+1) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = king;
7484         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7485         board[fromY][BOARD_RGHT-1] = EmptySquare;
7486     } else if (board[fromY][fromX] == king
7487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488                && toY == fromY && toX < fromX-1) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = king;
7491         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7492         board[fromY][BOARD_LEFT] = EmptySquare;
7493     } else if (fromY == 7 && fromX == 3
7494                && board[fromY][fromX] == BlackKing
7495                && toY == 7 && toX == 5) {
7496         board[fromY][fromX] = EmptySquare;
7497         board[toY][toX] = BlackKing;
7498         board[fromY][7] = EmptySquare;
7499         board[toY][4] = BlackRook;
7500     } else if (fromY == 7 && fromX == 3
7501                && board[fromY][fromX] == BlackKing
7502                && toY == 7 && toX == 1) {
7503         board[fromY][fromX] = EmptySquare;
7504         board[toY][toX] = BlackKing;
7505         board[fromY][0] = EmptySquare;
7506         board[toY][2] = BlackRook;
7507     } else if (board[fromY][fromX] == BlackPawn
7508                && toY == 0
7509                && gameInfo.variant != VariantXiangqi
7510                ) {
7511         /* black pawn promotion */
7512         board[0][toX] = CharToPiece(ToLower(promoChar));
7513         if (board[0][toX] == EmptySquare) {
7514             board[0][toX] = BlackQueen;
7515         }
7516         if(gameInfo.variant==VariantBughouse ||
7517            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7518             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7519         board[fromY][fromX] = EmptySquare;
7520     } else if ((fromY == 3)
7521                && (toX != fromX)
7522                && gameInfo.variant != VariantXiangqi
7523                && gameInfo.variant != VariantBerolina
7524                && (board[fromY][fromX] == BlackPawn)
7525                && (board[toY][toX] == EmptySquare)) {
7526         board[fromY][fromX] = EmptySquare;
7527         board[toY][toX] = BlackPawn;
7528         captured = board[toY + 1][toX];
7529         board[toY + 1][toX] = EmptySquare;
7530     } else if ((fromY == 3)
7531                && (toX == fromX)
7532                && gameInfo.variant == VariantBerolina
7533                && (board[fromY][fromX] == BlackPawn)
7534                && (board[toY][toX] == EmptySquare)) {
7535         board[fromY][fromX] = EmptySquare;
7536         board[toY][toX] = BlackPawn;
7537         if(oldEP & EP_BEROLIN_A) {
7538                 captured = board[fromY][fromX-1];
7539                 board[fromY][fromX-1] = EmptySquare;
7540         }else{  captured = board[fromY][fromX+1];
7541                 board[fromY][fromX+1] = EmptySquare;
7542         }
7543     } else {
7544         board[toY][toX] = board[fromY][fromX];
7545         board[fromY][fromX] = EmptySquare;
7546     }
7547
7548     /* [HGM] now we promote for Shogi, if needed */
7549     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7550         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7551   }
7552
7553     if (gameInfo.holdingsWidth != 0) {
7554
7555       /* !!A lot more code needs to be written to support holdings  */
7556       /* [HGM] OK, so I have written it. Holdings are stored in the */
7557       /* penultimate board files, so they are automaticlly stored   */
7558       /* in the game history.                                       */
7559       if (fromY == DROP_RANK) {
7560         /* Delete from holdings, by decreasing count */
7561         /* and erasing image if necessary            */
7562         p = (int) fromX;
7563         if(p < (int) BlackPawn) { /* white drop */
7564              p -= (int)WhitePawn;
7565                  p = PieceToNumber((ChessSquare)p);
7566              if(p >= gameInfo.holdingsSize) p = 0;
7567              if(--board[p][BOARD_WIDTH-2] <= 0)
7568                   board[p][BOARD_WIDTH-1] = EmptySquare;
7569              if((int)board[p][BOARD_WIDTH-2] < 0)
7570                         board[p][BOARD_WIDTH-2] = 0;
7571         } else {                  /* black drop */
7572              p -= (int)BlackPawn;
7573                  p = PieceToNumber((ChessSquare)p);
7574              if(p >= gameInfo.holdingsSize) p = 0;
7575              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7576                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7577              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7578                         board[BOARD_HEIGHT-1-p][1] = 0;
7579         }
7580       }
7581       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7582           && gameInfo.variant != VariantBughouse        ) {
7583         /* [HGM] holdings: Add to holdings, if holdings exist */
7584         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7585                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7586                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7587         }
7588         p = (int) captured;
7589         if (p >= (int) BlackPawn) {
7590           p -= (int)BlackPawn;
7591           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7592                   /* in Shogi restore piece to its original  first */
7593                   captured = (ChessSquare) (DEMOTED captured);
7594                   p = DEMOTED p;
7595           }
7596           p = PieceToNumber((ChessSquare)p);
7597           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7598           board[p][BOARD_WIDTH-2]++;
7599           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7600         } else {
7601           p -= (int)WhitePawn;
7602           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7603                   captured = (ChessSquare) (DEMOTED captured);
7604                   p = DEMOTED p;
7605           }
7606           p = PieceToNumber((ChessSquare)p);
7607           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7608           board[BOARD_HEIGHT-1-p][1]++;
7609           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7610         }
7611       }
7612     } else if (gameInfo.variant == VariantAtomic) {
7613       if (captured != EmptySquare) {
7614         int y, x;
7615         for (y = toY-1; y <= toY+1; y++) {
7616           for (x = toX-1; x <= toX+1; x++) {
7617             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7618                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7619               board[y][x] = EmptySquare;
7620             }
7621           }
7622         }
7623         board[toY][toX] = EmptySquare;
7624       }
7625     }
7626     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7627         /* [HGM] Shogi promotions */
7628         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7629     }
7630
7631     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7632                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7633         // [HGM] superchess: take promotion piece out of holdings
7634         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7635         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7636             if(!--board[k][BOARD_WIDTH-2])
7637                 board[k][BOARD_WIDTH-1] = EmptySquare;
7638         } else {
7639             if(!--board[BOARD_HEIGHT-1-k][1])
7640                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7641         }
7642     }
7643
7644 }
7645
7646 /* Updates forwardMostMove */
7647 void
7648 MakeMove(fromX, fromY, toX, toY, promoChar)
7649      int fromX, fromY, toX, toY;
7650      int promoChar;
7651 {
7652 //    forwardMostMove++; // [HGM] bare: moved downstream
7653
7654     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7655         int timeLeft; static int lastLoadFlag=0; int king, piece;
7656         piece = boards[forwardMostMove][fromY][fromX];
7657         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7658         if(gameInfo.variant == VariantKnightmate)
7659             king += (int) WhiteUnicorn - (int) WhiteKing;
7660         if(forwardMostMove == 0) {
7661             if(blackPlaysFirst) 
7662                 fprintf(serverMoves, "%s;", second.tidy);
7663             fprintf(serverMoves, "%s;", first.tidy);
7664             if(!blackPlaysFirst) 
7665                 fprintf(serverMoves, "%s;", second.tidy);
7666         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7667         lastLoadFlag = loadFlag;
7668         // print base move
7669         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7670         // print castling suffix
7671         if( toY == fromY && piece == king ) {
7672             if(toX-fromX > 1)
7673                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7674             if(fromX-toX >1)
7675                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7676         }
7677         // e.p. suffix
7678         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7679              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7680              boards[forwardMostMove][toY][toX] == EmptySquare
7681              && fromX != toX )
7682                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7683         // promotion suffix
7684         if(promoChar != NULLCHAR)
7685                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7686         if(!loadFlag) {
7687             fprintf(serverMoves, "/%d/%d",
7688                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7689             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7690             else                      timeLeft = blackTimeRemaining/1000;
7691             fprintf(serverMoves, "/%d", timeLeft);
7692         }
7693         fflush(serverMoves);
7694     }
7695
7696     if (forwardMostMove+1 >= MAX_MOVES) {
7697       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7698                         0, 1);
7699       return;
7700     }
7701     if (commentList[forwardMostMove+1] != NULL) {
7702         free(commentList[forwardMostMove+1]);
7703         commentList[forwardMostMove+1] = NULL;
7704     }
7705     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7706     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7707     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7708                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7709     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7710     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7711     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7712     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7713     gameInfo.result = GameUnfinished;
7714     if (gameInfo.resultDetails != NULL) {
7715         free(gameInfo.resultDetails);
7716         gameInfo.resultDetails = NULL;
7717     }
7718     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7719                               moveList[forwardMostMove - 1]);
7720     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7721                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7722                              fromY, fromX, toY, toX, promoChar,
7723                              parseList[forwardMostMove - 1]);
7724     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7725                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7726                             castlingRights[forwardMostMove]) ) {
7727       case MT_NONE:
7728       case MT_STALEMATE:
7729       default:
7730         break;
7731       case MT_CHECK:
7732         if(gameInfo.variant != VariantShogi)
7733             strcat(parseList[forwardMostMove - 1], "+");
7734         break;
7735       case MT_CHECKMATE:
7736       case MT_STAINMATE:
7737         strcat(parseList[forwardMostMove - 1], "#");
7738         break;
7739     }
7740     if (appData.debugMode) {
7741         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7742     }
7743
7744 }
7745
7746 /* Updates currentMove if not pausing */
7747 void
7748 ShowMove(fromX, fromY, toX, toY)
7749 {
7750     int instant = (gameMode == PlayFromGameFile) ?
7751         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7752     if(appData.noGUI) return;
7753     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7754         if (!instant) {
7755             if (forwardMostMove == currentMove + 1) {
7756                 AnimateMove(boards[forwardMostMove - 1],
7757                             fromX, fromY, toX, toY);
7758             }
7759             if (appData.highlightLastMove) {
7760                 SetHighlights(fromX, fromY, toX, toY);
7761             }
7762         }
7763         currentMove = forwardMostMove;
7764     }
7765
7766     if (instant) return;
7767
7768     DisplayMove(currentMove - 1);
7769     DrawPosition(FALSE, boards[currentMove]);
7770     DisplayBothClocks();
7771     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7772 }
7773
7774 void SendEgtPath(ChessProgramState *cps)
7775 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7776         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7777
7778         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7779
7780         while(*p) {
7781             char c, *q = name+1, *r, *s;
7782
7783             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7784             while(*p && *p != ',') *q++ = *p++;
7785             *q++ = ':'; *q = 0;
7786             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7787                 strcmp(name, ",nalimov:") == 0 ) {
7788                 // take nalimov path from the menu-changeable option first, if it is defined
7789                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7790                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7791             } else
7792             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7793                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7794                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7795                 s = r = StrStr(s, ":") + 1; // beginning of path info
7796                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7797                 c = *r; *r = 0;             // temporarily null-terminate path info
7798                     *--q = 0;               // strip of trailig ':' from name
7799                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7800                 *r = c;
7801                 SendToProgram(buf,cps);     // send egtbpath command for this format
7802             }
7803             if(*p == ',') p++; // read away comma to position for next format name
7804         }
7805 }
7806
7807 void
7808 InitChessProgram(cps, setup)
7809      ChessProgramState *cps;
7810      int setup; /* [HGM] needed to setup FRC opening position */
7811 {
7812     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7813     if (appData.noChessProgram) return;
7814     hintRequested = FALSE;
7815     bookRequested = FALSE;
7816
7817     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7818     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7819     if(cps->memSize) { /* [HGM] memory */
7820         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7821         SendToProgram(buf, cps);
7822     }
7823     SendEgtPath(cps); /* [HGM] EGT */
7824     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7825         sprintf(buf, "cores %d\n", appData.smpCores);
7826         SendToProgram(buf, cps);
7827     }
7828
7829     SendToProgram(cps->initString, cps);
7830     if (gameInfo.variant != VariantNormal &&
7831         gameInfo.variant != VariantLoadable
7832         /* [HGM] also send variant if board size non-standard */
7833         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7834                                             ) {
7835       char *v = VariantName(gameInfo.variant);
7836       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7837         /* [HGM] in protocol 1 we have to assume all variants valid */
7838         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7839         DisplayFatalError(buf, 0, 1);
7840         return;
7841       }
7842
7843       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7844       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7845       if( gameInfo.variant == VariantXiangqi )
7846            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7847       if( gameInfo.variant == VariantShogi )
7848            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7849       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7850            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7851       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7852                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7853            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7854       if( gameInfo.variant == VariantCourier )
7855            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7856       if( gameInfo.variant == VariantSuper )
7857            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7858       if( gameInfo.variant == VariantGreat )
7859            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7860
7861       if(overruled) {
7862            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7863                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7864            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7865            if(StrStr(cps->variants, b) == NULL) { 
7866                // specific sized variant not known, check if general sizing allowed
7867                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7868                    if(StrStr(cps->variants, "boardsize") == NULL) {
7869                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7870                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7871                        DisplayFatalError(buf, 0, 1);
7872                        return;
7873                    }
7874                    /* [HGM] here we really should compare with the maximum supported board size */
7875                }
7876            }
7877       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7878       sprintf(buf, "variant %s\n", b);
7879       SendToProgram(buf, cps);
7880     }
7881     currentlyInitializedVariant = gameInfo.variant;
7882
7883     /* [HGM] send opening position in FRC to first engine */
7884     if(setup) {
7885           SendToProgram("force\n", cps);
7886           SendBoard(cps, 0);
7887           /* engine is now in force mode! Set flag to wake it up after first move. */
7888           setboardSpoiledMachineBlack = 1;
7889     }
7890
7891     if (cps->sendICS) {
7892       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7893       SendToProgram(buf, cps);
7894     }
7895     cps->maybeThinking = FALSE;
7896     cps->offeredDraw = 0;
7897     if (!appData.icsActive) {
7898         SendTimeControl(cps, movesPerSession, timeControl,
7899                         timeIncrement, appData.searchDepth,
7900                         searchTime);
7901     }
7902     if (appData.showThinking 
7903         // [HGM] thinking: four options require thinking output to be sent
7904         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7905                                 ) {
7906         SendToProgram("post\n", cps);
7907     }
7908     SendToProgram("hard\n", cps);
7909     if (!appData.ponderNextMove) {
7910         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7911            it without being sure what state we are in first.  "hard"
7912            is not a toggle, so that one is OK.
7913          */
7914         SendToProgram("easy\n", cps);
7915     }
7916     if (cps->usePing) {
7917       sprintf(buf, "ping %d\n", ++cps->lastPing);
7918       SendToProgram(buf, cps);
7919     }
7920     cps->initDone = TRUE;
7921 }   
7922
7923
7924 void
7925 StartChessProgram(cps)
7926      ChessProgramState *cps;
7927 {
7928     char buf[MSG_SIZ];
7929     int err;
7930
7931     if (appData.noChessProgram) return;
7932     cps->initDone = FALSE;
7933
7934     if (strcmp(cps->host, "localhost") == 0) {
7935         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7936     } else if (*appData.remoteShell == NULLCHAR) {
7937         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7938     } else {
7939         if (*appData.remoteUser == NULLCHAR) {
7940           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7941                     cps->program);
7942         } else {
7943           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7944                     cps->host, appData.remoteUser, cps->program);
7945         }
7946         err = StartChildProcess(buf, "", &cps->pr);
7947     }
7948     
7949     if (err != 0) {
7950         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7951         DisplayFatalError(buf, err, 1);
7952         cps->pr = NoProc;
7953         cps->isr = NULL;
7954         return;
7955     }
7956     
7957     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7958     if (cps->protocolVersion > 1) {
7959       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7960       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7961       cps->comboCnt = 0;  //                and values of combo boxes
7962       SendToProgram(buf, cps);
7963     } else {
7964       SendToProgram("xboard\n", cps);
7965     }
7966 }
7967
7968
7969 void
7970 TwoMachinesEventIfReady P((void))
7971 {
7972   if (first.lastPing != first.lastPong) {
7973     DisplayMessage("", _("Waiting for first chess program"));
7974     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7975     return;
7976   }
7977   if (second.lastPing != second.lastPong) {
7978     DisplayMessage("", _("Waiting for second chess program"));
7979     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7980     return;
7981   }
7982   ThawUI();
7983   TwoMachinesEvent();
7984 }
7985
7986 void
7987 NextMatchGame P((void))
7988 {
7989     int index; /* [HGM] autoinc: step load index during match */
7990     Reset(FALSE, TRUE);
7991     if (*appData.loadGameFile != NULLCHAR) {
7992         index = appData.loadGameIndex;
7993         if(index < 0) { // [HGM] autoinc
7994             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7995             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7996         } 
7997         LoadGameFromFile(appData.loadGameFile,
7998                          index,
7999                          appData.loadGameFile, FALSE);
8000     } else if (*appData.loadPositionFile != NULLCHAR) {
8001         index = appData.loadPositionIndex;
8002         if(index < 0) { // [HGM] autoinc
8003             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8004             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8005         } 
8006         LoadPositionFromFile(appData.loadPositionFile,
8007                              index,
8008                              appData.loadPositionFile);
8009     }
8010     TwoMachinesEventIfReady();
8011 }
8012
8013 void UserAdjudicationEvent( int result )
8014 {
8015     ChessMove gameResult = GameIsDrawn;
8016
8017     if( result > 0 ) {
8018         gameResult = WhiteWins;
8019     }
8020     else if( result < 0 ) {
8021         gameResult = BlackWins;
8022     }
8023
8024     if( gameMode == TwoMachinesPlay ) {
8025         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8026     }
8027 }
8028
8029
8030 // [HGM] save: calculate checksum of game to make games easily identifiable
8031 int StringCheckSum(char *s)
8032 {
8033         int i = 0;
8034         if(s==NULL) return 0;
8035         while(*s) i = i*259 + *s++;
8036         return i;
8037 }
8038
8039 int GameCheckSum()
8040 {
8041         int i, sum=0;
8042         for(i=backwardMostMove; i<forwardMostMove; i++) {
8043                 sum += pvInfoList[i].depth;
8044                 sum += StringCheckSum(parseList[i]);
8045                 sum += StringCheckSum(commentList[i]);
8046                 sum *= 261;
8047         }
8048         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8049         return sum + StringCheckSum(commentList[i]);
8050 } // end of save patch
8051
8052 void
8053 GameEnds(result, resultDetails, whosays)
8054      ChessMove result;
8055      char *resultDetails;
8056      int whosays;
8057 {
8058     GameMode nextGameMode;
8059     int isIcsGame;
8060     char buf[MSG_SIZ];
8061
8062     if(endingGame) return; /* [HGM] crash: forbid recursion */
8063     endingGame = 1;
8064
8065     if (appData.debugMode) {
8066       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8067               result, resultDetails ? resultDetails : "(null)", whosays);
8068     }
8069
8070     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8071         /* If we are playing on ICS, the server decides when the
8072            game is over, but the engine can offer to draw, claim 
8073            a draw, or resign. 
8074          */
8075 #if ZIPPY
8076         if (appData.zippyPlay && first.initDone) {
8077             if (result == GameIsDrawn) {
8078                 /* In case draw still needs to be claimed */
8079                 SendToICS(ics_prefix);
8080                 SendToICS("draw\n");
8081             } else if (StrCaseStr(resultDetails, "resign")) {
8082                 SendToICS(ics_prefix);
8083                 SendToICS("resign\n");
8084             }
8085         }
8086 #endif
8087         endingGame = 0; /* [HGM] crash */
8088         return;
8089     }
8090
8091     /* If we're loading the game from a file, stop */
8092     if (whosays == GE_FILE) {
8093       (void) StopLoadGameTimer();
8094       gameFileFP = NULL;
8095     }
8096
8097     /* Cancel draw offers */
8098     first.offeredDraw = second.offeredDraw = 0;
8099
8100     /* If this is an ICS game, only ICS can really say it's done;
8101        if not, anyone can. */
8102     isIcsGame = (gameMode == IcsPlayingWhite || 
8103                  gameMode == IcsPlayingBlack || 
8104                  gameMode == IcsObserving    || 
8105                  gameMode == IcsExamining);
8106
8107     if (!isIcsGame || whosays == GE_ICS) {
8108         /* OK -- not an ICS game, or ICS said it was done */
8109         StopClocks();
8110         if (!isIcsGame && !appData.noChessProgram) 
8111           SetUserThinkingEnables();
8112     
8113         /* [HGM] if a machine claims the game end we verify this claim */
8114         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8115             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8116                 char claimer;
8117                 ChessMove trueResult = (ChessMove) -1;
8118
8119                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8120                                             first.twoMachinesColor[0] :
8121                                             second.twoMachinesColor[0] ;
8122
8123                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8124                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8125                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8126                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8127                 } else
8128                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8129                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8130                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8131                 } else
8132                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8133                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8134                 }
8135
8136                 // now verify win claims, but not in drop games, as we don't understand those yet
8137                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8138                                                  || gameInfo.variant == VariantGreat) &&
8139                     (result == WhiteWins && claimer == 'w' ||
8140                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8141                       if (appData.debugMode) {
8142                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8143                                 result, epStatus[forwardMostMove], forwardMostMove);
8144                       }
8145                       if(result != trueResult) {
8146                               sprintf(buf, "False win claim: '%s'", resultDetails);
8147                               result = claimer == 'w' ? BlackWins : WhiteWins;
8148                               resultDetails = buf;
8149                       }
8150                 } else
8151                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8152                     && (forwardMostMove <= backwardMostMove ||
8153                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8154                         (claimer=='b')==(forwardMostMove&1))
8155                                                                                   ) {
8156                       /* [HGM] verify: draws that were not flagged are false claims */
8157                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8158                       result = claimer == 'w' ? BlackWins : WhiteWins;
8159                       resultDetails = buf;
8160                 }
8161                 /* (Claiming a loss is accepted no questions asked!) */
8162             }
8163             /* [HGM] bare: don't allow bare King to win */
8164             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8165                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8166                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8167                && result != GameIsDrawn)
8168             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8169                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8170                         int p = (int)boards[forwardMostMove][i][j] - color;
8171                         if(p >= 0 && p <= (int)WhiteKing) k++;
8172                 }
8173                 if (appData.debugMode) {
8174                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8175                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8176                 }
8177                 if(k <= 1) {
8178                         result = GameIsDrawn;
8179                         sprintf(buf, "%s but bare king", resultDetails);
8180                         resultDetails = buf;
8181                 }
8182             }
8183         }
8184
8185
8186         if(serverMoves != NULL && !loadFlag) { char c = '=';
8187             if(result==WhiteWins) c = '+';
8188             if(result==BlackWins) c = '-';
8189             if(resultDetails != NULL)
8190                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8191         }
8192         if (resultDetails != NULL) {
8193             gameInfo.result = result;
8194             gameInfo.resultDetails = StrSave(resultDetails);
8195
8196             /* display last move only if game was not loaded from file */
8197             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8198                 DisplayMove(currentMove - 1);
8199     
8200             if (forwardMostMove != 0) {
8201                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8202                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8203                                                                 ) {
8204                     if (*appData.saveGameFile != NULLCHAR) {
8205                         SaveGameToFile(appData.saveGameFile, TRUE);
8206                     } else if (appData.autoSaveGames) {
8207                         AutoSaveGame();
8208                     }
8209                     if (*appData.savePositionFile != NULLCHAR) {
8210                         SavePositionToFile(appData.savePositionFile);
8211                     }
8212                 }
8213             }
8214
8215             /* Tell program how game ended in case it is learning */
8216             /* [HGM] Moved this to after saving the PGN, just in case */
8217             /* engine died and we got here through time loss. In that */
8218             /* case we will get a fatal error writing the pipe, which */
8219             /* would otherwise lose us the PGN.                       */
8220             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8221             /* output during GameEnds should never be fatal anymore   */
8222             if (gameMode == MachinePlaysWhite ||
8223                 gameMode == MachinePlaysBlack ||
8224                 gameMode == TwoMachinesPlay ||
8225                 gameMode == IcsPlayingWhite ||
8226                 gameMode == IcsPlayingBlack ||
8227                 gameMode == BeginningOfGame) {
8228                 char buf[MSG_SIZ];
8229                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8230                         resultDetails);
8231                 if (first.pr != NoProc) {
8232                     SendToProgram(buf, &first);
8233                 }
8234                 if (second.pr != NoProc &&
8235                     gameMode == TwoMachinesPlay) {
8236                     SendToProgram(buf, &second);
8237                 }
8238             }
8239         }
8240
8241         if (appData.icsActive) {
8242             if (appData.quietPlay &&
8243                 (gameMode == IcsPlayingWhite ||
8244                  gameMode == IcsPlayingBlack)) {
8245                 SendToICS(ics_prefix);
8246                 SendToICS("set shout 1\n");
8247             }
8248             nextGameMode = IcsIdle;
8249             ics_user_moved = FALSE;
8250             /* clean up premove.  It's ugly when the game has ended and the
8251              * premove highlights are still on the board.
8252              */
8253             if (gotPremove) {
8254               gotPremove = FALSE;
8255               ClearPremoveHighlights();
8256               DrawPosition(FALSE, boards[currentMove]);
8257             }
8258             if (whosays == GE_ICS) {
8259                 switch (result) {
8260                 case WhiteWins:
8261                     if (gameMode == IcsPlayingWhite)
8262                         PlayIcsWinSound();
8263                     else if(gameMode == IcsPlayingBlack)
8264                         PlayIcsLossSound();
8265                     break;
8266                 case BlackWins:
8267                     if (gameMode == IcsPlayingBlack)
8268                         PlayIcsWinSound();
8269                     else if(gameMode == IcsPlayingWhite)
8270                         PlayIcsLossSound();
8271                     break;
8272                 case GameIsDrawn:
8273                     PlayIcsDrawSound();
8274                     break;
8275                 default:
8276                     PlayIcsUnfinishedSound();
8277                 }
8278             }
8279         } else if (gameMode == EditGame ||
8280                    gameMode == PlayFromGameFile || 
8281                    gameMode == AnalyzeMode || 
8282                    gameMode == AnalyzeFile) {
8283             nextGameMode = gameMode;
8284         } else {
8285             nextGameMode = EndOfGame;
8286         }
8287         pausing = FALSE;
8288         ModeHighlight();
8289     } else {
8290         nextGameMode = gameMode;
8291     }
8292
8293     if (appData.noChessProgram) {
8294         gameMode = nextGameMode;
8295         ModeHighlight();
8296         endingGame = 0; /* [HGM] crash */
8297         return;
8298     }
8299
8300     if (first.reuse) {
8301         /* Put first chess program into idle state */
8302         if (first.pr != NoProc &&
8303             (gameMode == MachinePlaysWhite ||
8304              gameMode == MachinePlaysBlack ||
8305              gameMode == TwoMachinesPlay ||
8306              gameMode == IcsPlayingWhite ||
8307              gameMode == IcsPlayingBlack ||
8308              gameMode == BeginningOfGame)) {
8309             SendToProgram("force\n", &first);
8310             if (first.usePing) {
8311               char buf[MSG_SIZ];
8312               sprintf(buf, "ping %d\n", ++first.lastPing);
8313               SendToProgram(buf, &first);
8314             }
8315         }
8316     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8317         /* Kill off first chess program */
8318         if (first.isr != NULL)
8319           RemoveInputSource(first.isr);
8320         first.isr = NULL;
8321     
8322         if (first.pr != NoProc) {
8323             ExitAnalyzeMode();
8324             DoSleep( appData.delayBeforeQuit );
8325             SendToProgram("quit\n", &first);
8326             DoSleep( appData.delayAfterQuit );
8327             DestroyChildProcess(first.pr, first.useSigterm);
8328         }
8329         first.pr = NoProc;
8330     }
8331     if (second.reuse) {
8332         /* Put second chess program into idle state */
8333         if (second.pr != NoProc &&
8334             gameMode == TwoMachinesPlay) {
8335             SendToProgram("force\n", &second);
8336             if (second.usePing) {
8337               char buf[MSG_SIZ];
8338               sprintf(buf, "ping %d\n", ++second.lastPing);
8339               SendToProgram(buf, &second);
8340             }
8341         }
8342     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8343         /* Kill off second chess program */
8344         if (second.isr != NULL)
8345           RemoveInputSource(second.isr);
8346         second.isr = NULL;
8347     
8348         if (second.pr != NoProc) {
8349             DoSleep( appData.delayBeforeQuit );
8350             SendToProgram("quit\n", &second);
8351             DoSleep( appData.delayAfterQuit );
8352             DestroyChildProcess(second.pr, second.useSigterm);
8353         }
8354         second.pr = NoProc;
8355     }
8356
8357     if (matchMode && gameMode == TwoMachinesPlay) {
8358         switch (result) {
8359         case WhiteWins:
8360           if (first.twoMachinesColor[0] == 'w') {
8361             first.matchWins++;
8362           } else {
8363             second.matchWins++;
8364           }
8365           break;
8366         case BlackWins:
8367           if (first.twoMachinesColor[0] == 'b') {
8368             first.matchWins++;
8369           } else {
8370             second.matchWins++;
8371           }
8372           break;
8373         default:
8374           break;
8375         }
8376         if (matchGame < appData.matchGames) {
8377             char *tmp;
8378             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8379                 tmp = first.twoMachinesColor;
8380                 first.twoMachinesColor = second.twoMachinesColor;
8381                 second.twoMachinesColor = tmp;
8382             }
8383             gameMode = nextGameMode;
8384             matchGame++;
8385             if(appData.matchPause>10000 || appData.matchPause<10)
8386                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8387             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8388             endingGame = 0; /* [HGM] crash */
8389             return;
8390         } else {
8391             char buf[MSG_SIZ];
8392             gameMode = nextGameMode;
8393             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8394                     first.tidy, second.tidy,
8395                     first.matchWins, second.matchWins,
8396                     appData.matchGames - (first.matchWins + second.matchWins));
8397             DisplayFatalError(buf, 0, 0);
8398         }
8399     }
8400     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8401         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8402       ExitAnalyzeMode();
8403     gameMode = nextGameMode;
8404     ModeHighlight();
8405     endingGame = 0;  /* [HGM] crash */
8406 }
8407
8408 /* Assumes program was just initialized (initString sent).
8409    Leaves program in force mode. */
8410 void
8411 FeedMovesToProgram(cps, upto) 
8412      ChessProgramState *cps;
8413      int upto;
8414 {
8415     int i;
8416     
8417     if (appData.debugMode)
8418       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8419               startedFromSetupPosition ? "position and " : "",
8420               backwardMostMove, upto, cps->which);
8421     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8422         // [HGM] variantswitch: make engine aware of new variant
8423         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8424                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8425         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8426         SendToProgram(buf, cps);
8427         currentlyInitializedVariant = gameInfo.variant;
8428     }
8429     SendToProgram("force\n", cps);
8430     if (startedFromSetupPosition) {
8431         SendBoard(cps, backwardMostMove);
8432     if (appData.debugMode) {
8433         fprintf(debugFP, "feedMoves\n");
8434     }
8435     }
8436     for (i = backwardMostMove; i < upto; i++) {
8437         SendMoveToProgram(i, cps);
8438     }
8439 }
8440
8441
8442 void
8443 ResurrectChessProgram()
8444 {
8445      /* The chess program may have exited.
8446         If so, restart it and feed it all the moves made so far. */
8447
8448     if (appData.noChessProgram || first.pr != NoProc) return;
8449     
8450     StartChessProgram(&first);
8451     InitChessProgram(&first, FALSE);
8452     FeedMovesToProgram(&first, currentMove);
8453
8454     if (!first.sendTime) {
8455         /* can't tell gnuchess what its clock should read,
8456            so we bow to its notion. */
8457         ResetClocks();
8458         timeRemaining[0][currentMove] = whiteTimeRemaining;
8459         timeRemaining[1][currentMove] = blackTimeRemaining;
8460     }
8461
8462     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8463                 appData.icsEngineAnalyze) && first.analysisSupport) {
8464       SendToProgram("analyze\n", &first);
8465       first.analyzing = TRUE;
8466     }
8467 }
8468
8469 /*
8470  * Button procedures
8471  */
8472 void
8473 Reset(redraw, init)
8474      int redraw, init;
8475 {
8476     int i;
8477
8478     if (appData.debugMode) {
8479         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8480                 redraw, init, gameMode);
8481     }
8482     pausing = pauseExamInvalid = FALSE;
8483     startedFromSetupPosition = blackPlaysFirst = FALSE;
8484     firstMove = TRUE;
8485     whiteFlag = blackFlag = FALSE;
8486     userOfferedDraw = FALSE;
8487     hintRequested = bookRequested = FALSE;
8488     first.maybeThinking = FALSE;
8489     second.maybeThinking = FALSE;
8490     first.bookSuspend = FALSE; // [HGM] book
8491     second.bookSuspend = FALSE;
8492     thinkOutput[0] = NULLCHAR;
8493     lastHint[0] = NULLCHAR;
8494     ClearGameInfo(&gameInfo);
8495     gameInfo.variant = StringToVariant(appData.variant);
8496     ics_user_moved = ics_clock_paused = FALSE;
8497     ics_getting_history = H_FALSE;
8498     ics_gamenum = -1;
8499     white_holding[0] = black_holding[0] = NULLCHAR;
8500     ClearProgramStats();
8501     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8502     
8503     ResetFrontEnd();
8504     ClearHighlights();
8505     flipView = appData.flipView;
8506     ClearPremoveHighlights();
8507     gotPremove = FALSE;
8508     alarmSounded = FALSE;
8509
8510     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8511     if(appData.serverMovesName != NULL) {
8512         /* [HGM] prepare to make moves file for broadcasting */
8513         clock_t t = clock();
8514         if(serverMoves != NULL) fclose(serverMoves);
8515         serverMoves = fopen(appData.serverMovesName, "r");
8516         if(serverMoves != NULL) {
8517             fclose(serverMoves);
8518             /* delay 15 sec before overwriting, so all clients can see end */
8519             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8520         }
8521         serverMoves = fopen(appData.serverMovesName, "w");
8522     }
8523
8524     ExitAnalyzeMode();
8525     gameMode = BeginningOfGame;
8526     ModeHighlight();
8527     if(appData.icsActive) gameInfo.variant = VariantNormal;
8528     currentMove = forwardMostMove = backwardMostMove = 0;
8529     InitPosition(redraw);
8530     for (i = 0; i < MAX_MOVES; i++) {
8531         if (commentList[i] != NULL) {
8532             free(commentList[i]);
8533             commentList[i] = NULL;
8534         }
8535     }
8536     ResetClocks();
8537     timeRemaining[0][0] = whiteTimeRemaining;
8538     timeRemaining[1][0] = blackTimeRemaining;
8539     if (first.pr == NULL) {
8540         StartChessProgram(&first);
8541     }
8542     if (init) {
8543             InitChessProgram(&first, startedFromSetupPosition);
8544     }
8545     DisplayTitle("");
8546     DisplayMessage("", "");
8547     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8548     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8549 }
8550
8551 void
8552 AutoPlayGameLoop()
8553 {
8554     for (;;) {
8555         if (!AutoPlayOneMove())
8556           return;
8557         if (matchMode || appData.timeDelay == 0)
8558           continue;
8559         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8560           return;
8561         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8562         break;
8563     }
8564 }
8565
8566
8567 int
8568 AutoPlayOneMove()
8569 {
8570     int fromX, fromY, toX, toY;
8571
8572     if (appData.debugMode) {
8573       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8574     }
8575
8576     if (gameMode != PlayFromGameFile)
8577       return FALSE;
8578
8579     if (currentMove >= forwardMostMove) {
8580       gameMode = EditGame;
8581       ModeHighlight();
8582
8583       /* [AS] Clear current move marker at the end of a game */
8584       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8585
8586       return FALSE;
8587     }
8588     
8589     toX = moveList[currentMove][2] - AAA;
8590     toY = moveList[currentMove][3] - ONE;
8591
8592     if (moveList[currentMove][1] == '@') {
8593         if (appData.highlightLastMove) {
8594             SetHighlights(-1, -1, toX, toY);
8595         }
8596     } else {
8597         fromX = moveList[currentMove][0] - AAA;
8598         fromY = moveList[currentMove][1] - ONE;
8599
8600         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8601
8602         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8603
8604         if (appData.highlightLastMove) {
8605             SetHighlights(fromX, fromY, toX, toY);
8606         }
8607     }
8608     DisplayMove(currentMove);
8609     SendMoveToProgram(currentMove++, &first);
8610     DisplayBothClocks();
8611     DrawPosition(FALSE, boards[currentMove]);
8612     // [HGM] PV info: always display, routine tests if empty
8613     DisplayComment(currentMove - 1, commentList[currentMove]);
8614     return TRUE;
8615 }
8616
8617
8618 int
8619 LoadGameOneMove(readAhead)
8620      ChessMove readAhead;
8621 {
8622     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8623     char promoChar = NULLCHAR;
8624     ChessMove moveType;
8625     char move[MSG_SIZ];
8626     char *p, *q;
8627     
8628     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8629         gameMode != AnalyzeMode && gameMode != Training) {
8630         gameFileFP = NULL;
8631         return FALSE;
8632     }
8633     
8634     yyboardindex = forwardMostMove;
8635     if (readAhead != (ChessMove)0) {
8636       moveType = readAhead;
8637     } else {
8638       if (gameFileFP == NULL)
8639           return FALSE;
8640       moveType = (ChessMove) yylex();
8641     }
8642     
8643     done = FALSE;
8644     switch (moveType) {
8645       case Comment:
8646         if (appData.debugMode) 
8647           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8648         p = yy_text;
8649         if (*p == '{' || *p == '[' || *p == '(') {
8650             p[strlen(p) - 1] = NULLCHAR;
8651             p++;
8652         }
8653
8654         /* append the comment but don't display it */
8655         while (*p == '\n') p++;
8656         AppendComment(currentMove, p);
8657         return TRUE;
8658
8659       case WhiteCapturesEnPassant:
8660       case BlackCapturesEnPassant:
8661       case WhitePromotionChancellor:
8662       case BlackPromotionChancellor:
8663       case WhitePromotionArchbishop:
8664       case BlackPromotionArchbishop:
8665       case WhitePromotionCentaur:
8666       case BlackPromotionCentaur:
8667       case WhitePromotionQueen:
8668       case BlackPromotionQueen:
8669       case WhitePromotionRook:
8670       case BlackPromotionRook:
8671       case WhitePromotionBishop:
8672       case BlackPromotionBishop:
8673       case WhitePromotionKnight:
8674       case BlackPromotionKnight:
8675       case WhitePromotionKing:
8676       case BlackPromotionKing:
8677       case NormalMove:
8678       case WhiteKingSideCastle:
8679       case WhiteQueenSideCastle:
8680       case BlackKingSideCastle:
8681       case BlackQueenSideCastle:
8682       case WhiteKingSideCastleWild:
8683       case WhiteQueenSideCastleWild:
8684       case BlackKingSideCastleWild:
8685       case BlackQueenSideCastleWild:
8686       /* PUSH Fabien */
8687       case WhiteHSideCastleFR:
8688       case WhiteASideCastleFR:
8689       case BlackHSideCastleFR:
8690       case BlackASideCastleFR:
8691       /* POP Fabien */
8692         if (appData.debugMode)
8693           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8694         fromX = currentMoveString[0] - AAA;
8695         fromY = currentMoveString[1] - ONE;
8696         toX = currentMoveString[2] - AAA;
8697         toY = currentMoveString[3] - ONE;
8698         promoChar = currentMoveString[4];
8699         break;
8700
8701       case WhiteDrop:
8702       case BlackDrop:
8703         if (appData.debugMode)
8704           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8705         fromX = moveType == WhiteDrop ?
8706           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8707         (int) CharToPiece(ToLower(currentMoveString[0]));
8708         fromY = DROP_RANK;
8709         toX = currentMoveString[2] - AAA;
8710         toY = currentMoveString[3] - ONE;
8711         break;
8712
8713       case WhiteWins:
8714       case BlackWins:
8715       case GameIsDrawn:
8716       case GameUnfinished:
8717         if (appData.debugMode)
8718           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8719         p = strchr(yy_text, '{');
8720         if (p == NULL) p = strchr(yy_text, '(');
8721         if (p == NULL) {
8722             p = yy_text;
8723             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8724         } else {
8725             q = strchr(p, *p == '{' ? '}' : ')');
8726             if (q != NULL) *q = NULLCHAR;
8727             p++;
8728         }
8729         GameEnds(moveType, p, GE_FILE);
8730         done = TRUE;
8731         if (cmailMsgLoaded) {
8732             ClearHighlights();
8733             flipView = WhiteOnMove(currentMove);
8734             if (moveType == GameUnfinished) flipView = !flipView;
8735             if (appData.debugMode)
8736               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8737         }
8738         break;
8739
8740       case (ChessMove) 0:       /* end of file */
8741         if (appData.debugMode)
8742           fprintf(debugFP, "Parser hit end of file\n");
8743         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8744                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8745           case MT_NONE:
8746           case MT_CHECK:
8747             break;
8748           case MT_CHECKMATE:
8749           case MT_STAINMATE:
8750             if (WhiteOnMove(currentMove)) {
8751                 GameEnds(BlackWins, "Black mates", GE_FILE);
8752             } else {
8753                 GameEnds(WhiteWins, "White mates", GE_FILE);
8754             }
8755             break;
8756           case MT_STALEMATE:
8757             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8758             break;
8759         }
8760         done = TRUE;
8761         break;
8762
8763       case MoveNumberOne:
8764         if (lastLoadGameStart == GNUChessGame) {
8765             /* GNUChessGames have numbers, but they aren't move numbers */
8766             if (appData.debugMode)
8767               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8768                       yy_text, (int) moveType);
8769             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8770         }
8771         /* else fall thru */
8772
8773       case XBoardGame:
8774       case GNUChessGame:
8775       case PGNTag:
8776         /* Reached start of next game in file */
8777         if (appData.debugMode)
8778           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8779         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8780                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8781           case MT_NONE:
8782           case MT_CHECK:
8783             break;
8784           case MT_CHECKMATE:
8785           case MT_STAINMATE:
8786             if (WhiteOnMove(currentMove)) {
8787                 GameEnds(BlackWins, "Black mates", GE_FILE);
8788             } else {
8789                 GameEnds(WhiteWins, "White mates", GE_FILE);
8790             }
8791             break;
8792           case MT_STALEMATE:
8793             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8794             break;
8795         }
8796         done = TRUE;
8797         break;
8798
8799       case PositionDiagram:     /* should not happen; ignore */
8800       case ElapsedTime:         /* ignore */
8801       case NAG:                 /* ignore */
8802         if (appData.debugMode)
8803           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8804                   yy_text, (int) moveType);
8805         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8806
8807       case IllegalMove:
8808         if (appData.testLegality) {
8809             if (appData.debugMode)
8810               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8811             sprintf(move, _("Illegal move: %d.%s%s"),
8812                     (forwardMostMove / 2) + 1,
8813                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8814             DisplayError(move, 0);
8815             done = TRUE;
8816         } else {
8817             if (appData.debugMode)
8818               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8819                       yy_text, currentMoveString);
8820             fromX = currentMoveString[0] - AAA;
8821             fromY = currentMoveString[1] - ONE;
8822             toX = currentMoveString[2] - AAA;
8823             toY = currentMoveString[3] - ONE;
8824             promoChar = currentMoveString[4];
8825         }
8826         break;
8827
8828       case AmbiguousMove:
8829         if (appData.debugMode)
8830           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8831         sprintf(move, _("Ambiguous move: %d.%s%s"),
8832                 (forwardMostMove / 2) + 1,
8833                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8834         DisplayError(move, 0);
8835         done = TRUE;
8836         break;
8837
8838       default:
8839       case ImpossibleMove:
8840         if (appData.debugMode)
8841           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8842         sprintf(move, _("Illegal move: %d.%s%s"),
8843                 (forwardMostMove / 2) + 1,
8844                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8845         DisplayError(move, 0);
8846         done = TRUE;
8847         break;
8848     }
8849
8850     if (done) {
8851         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8852             DrawPosition(FALSE, boards[currentMove]);
8853             DisplayBothClocks();
8854             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8855               DisplayComment(currentMove - 1, commentList[currentMove]);
8856         }
8857         (void) StopLoadGameTimer();
8858         gameFileFP = NULL;
8859         cmailOldMove = forwardMostMove;
8860         return FALSE;
8861     } else {
8862         /* currentMoveString is set as a side-effect of yylex */
8863         strcat(currentMoveString, "\n");
8864         strcpy(moveList[forwardMostMove], currentMoveString);
8865         
8866         thinkOutput[0] = NULLCHAR;
8867         MakeMove(fromX, fromY, toX, toY, promoChar);
8868         currentMove = forwardMostMove;
8869         return TRUE;
8870     }
8871 }
8872
8873 /* Load the nth game from the given file */
8874 int
8875 LoadGameFromFile(filename, n, title, useList)
8876      char *filename;
8877      int n;
8878      char *title;
8879      /*Boolean*/ int useList;
8880 {
8881     FILE *f;
8882     char buf[MSG_SIZ];
8883
8884     if (strcmp(filename, "-") == 0) {
8885         f = stdin;
8886         title = "stdin";
8887     } else {
8888         f = fopen(filename, "rb");
8889         if (f == NULL) {
8890           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8891             DisplayError(buf, errno);
8892             return FALSE;
8893         }
8894     }
8895     if (fseek(f, 0, 0) == -1) {
8896         /* f is not seekable; probably a pipe */
8897         useList = FALSE;
8898     }
8899     if (useList && n == 0) {
8900         int error = GameListBuild(f);
8901         if (error) {
8902             DisplayError(_("Cannot build game list"), error);
8903         } else if (!ListEmpty(&gameList) &&
8904                    ((ListGame *) gameList.tailPred)->number > 1) {
8905             GameListPopUp(f, title);
8906             return TRUE;
8907         }
8908         GameListDestroy();
8909         n = 1;
8910     }
8911     if (n == 0) n = 1;
8912     return LoadGame(f, n, title, FALSE);
8913 }
8914
8915
8916 void
8917 MakeRegisteredMove()
8918 {
8919     int fromX, fromY, toX, toY;
8920     char promoChar;
8921     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8922         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8923           case CMAIL_MOVE:
8924           case CMAIL_DRAW:
8925             if (appData.debugMode)
8926               fprintf(debugFP, "Restoring %s for game %d\n",
8927                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8928     
8929             thinkOutput[0] = NULLCHAR;
8930             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8931             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8932             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8933             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8934             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8935             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8936             MakeMove(fromX, fromY, toX, toY, promoChar);
8937             ShowMove(fromX, fromY, toX, toY);
8938               
8939             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8940                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8941               case MT_NONE:
8942               case MT_CHECK:
8943                 break;
8944                 
8945               case MT_CHECKMATE:
8946               case MT_STAINMATE:
8947                 if (WhiteOnMove(currentMove)) {
8948                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8949                 } else {
8950                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8951                 }
8952                 break;
8953                 
8954               case MT_STALEMATE:
8955                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8956                 break;
8957             }
8958
8959             break;
8960             
8961           case CMAIL_RESIGN:
8962             if (WhiteOnMove(currentMove)) {
8963                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8964             } else {
8965                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8966             }
8967             break;
8968             
8969           case CMAIL_ACCEPT:
8970             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8971             break;
8972               
8973           default:
8974             break;
8975         }
8976     }
8977
8978     return;
8979 }
8980
8981 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8982 int
8983 CmailLoadGame(f, gameNumber, title, useList)
8984      FILE *f;
8985      int gameNumber;
8986      char *title;
8987      int useList;
8988 {
8989     int retVal;
8990
8991     if (gameNumber > nCmailGames) {
8992         DisplayError(_("No more games in this message"), 0);
8993         return FALSE;
8994     }
8995     if (f == lastLoadGameFP) {
8996         int offset = gameNumber - lastLoadGameNumber;
8997         if (offset == 0) {
8998             cmailMsg[0] = NULLCHAR;
8999             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9000                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9001                 nCmailMovesRegistered--;
9002             }
9003             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9004             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9005                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9006             }
9007         } else {
9008             if (! RegisterMove()) return FALSE;
9009         }
9010     }
9011
9012     retVal = LoadGame(f, gameNumber, title, useList);
9013
9014     /* Make move registered during previous look at this game, if any */
9015     MakeRegisteredMove();
9016
9017     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9018         commentList[currentMove]
9019           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9020         DisplayComment(currentMove - 1, commentList[currentMove]);
9021     }
9022
9023     return retVal;
9024 }
9025
9026 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9027 int
9028 ReloadGame(offset)
9029      int offset;
9030 {
9031     int gameNumber = lastLoadGameNumber + offset;
9032     if (lastLoadGameFP == NULL) {
9033         DisplayError(_("No game has been loaded yet"), 0);
9034         return FALSE;
9035     }
9036     if (gameNumber <= 0) {
9037         DisplayError(_("Can't back up any further"), 0);
9038         return FALSE;
9039     }
9040     if (cmailMsgLoaded) {
9041         return CmailLoadGame(lastLoadGameFP, gameNumber,
9042                              lastLoadGameTitle, lastLoadGameUseList);
9043     } else {
9044         return LoadGame(lastLoadGameFP, gameNumber,
9045                         lastLoadGameTitle, lastLoadGameUseList);
9046     }
9047 }
9048
9049
9050
9051 /* Load the nth game from open file f */
9052 int
9053 LoadGame(f, gameNumber, title, useList)
9054      FILE *f;
9055      int gameNumber;
9056      char *title;
9057      int useList;
9058 {
9059     ChessMove cm;
9060     char buf[MSG_SIZ];
9061     int gn = gameNumber;
9062     ListGame *lg = NULL;
9063     int numPGNTags = 0;
9064     int err;
9065     GameMode oldGameMode;
9066     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9067
9068     if (appData.debugMode) 
9069         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9070
9071     if (gameMode == Training )
9072         SetTrainingModeOff();
9073
9074     oldGameMode = gameMode;
9075     if (gameMode != BeginningOfGame) {
9076       Reset(FALSE, TRUE);
9077     }
9078
9079     gameFileFP = f;
9080     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9081         fclose(lastLoadGameFP);
9082     }
9083
9084     if (useList) {
9085         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9086         
9087         if (lg) {
9088             fseek(f, lg->offset, 0);
9089             GameListHighlight(gameNumber);
9090             gn = 1;
9091         }
9092         else {
9093             DisplayError(_("Game number out of range"), 0);
9094             return FALSE;
9095         }
9096     } else {
9097         GameListDestroy();
9098         if (fseek(f, 0, 0) == -1) {
9099             if (f == lastLoadGameFP ?
9100                 gameNumber == lastLoadGameNumber + 1 :
9101                 gameNumber == 1) {
9102                 gn = 1;
9103             } else {
9104                 DisplayError(_("Can't seek on game file"), 0);
9105                 return FALSE;
9106             }
9107         }
9108     }
9109     lastLoadGameFP = f;
9110     lastLoadGameNumber = gameNumber;
9111     strcpy(lastLoadGameTitle, title);
9112     lastLoadGameUseList = useList;
9113
9114     yynewfile(f);
9115
9116     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9117       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9118                 lg->gameInfo.black);
9119             DisplayTitle(buf);
9120     } else if (*title != NULLCHAR) {
9121         if (gameNumber > 1) {
9122             sprintf(buf, "%s %d", title, gameNumber);
9123             DisplayTitle(buf);
9124         } else {
9125             DisplayTitle(title);
9126         }
9127     }
9128
9129     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9130         gameMode = PlayFromGameFile;
9131         ModeHighlight();
9132     }
9133
9134     currentMove = forwardMostMove = backwardMostMove = 0;
9135     CopyBoard(boards[0], initialPosition);
9136     StopClocks();
9137
9138     /*
9139      * Skip the first gn-1 games in the file.
9140      * Also skip over anything that precedes an identifiable 
9141      * start of game marker, to avoid being confused by 
9142      * garbage at the start of the file.  Currently 
9143      * recognized start of game markers are the move number "1",
9144      * the pattern "gnuchess .* game", the pattern
9145      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9146      * A game that starts with one of the latter two patterns
9147      * will also have a move number 1, possibly
9148      * following a position diagram.
9149      * 5-4-02: Let's try being more lenient and allowing a game to
9150      * start with an unnumbered move.  Does that break anything?
9151      */
9152     cm = lastLoadGameStart = (ChessMove) 0;
9153     while (gn > 0) {
9154         yyboardindex = forwardMostMove;
9155         cm = (ChessMove) yylex();
9156         switch (cm) {
9157           case (ChessMove) 0:
9158             if (cmailMsgLoaded) {
9159                 nCmailGames = CMAIL_MAX_GAMES - gn;
9160             } else {
9161                 Reset(TRUE, TRUE);
9162                 DisplayError(_("Game not found in file"), 0);
9163             }
9164             return FALSE;
9165
9166           case GNUChessGame:
9167           case XBoardGame:
9168             gn--;
9169             lastLoadGameStart = cm;
9170             break;
9171             
9172           case MoveNumberOne:
9173             switch (lastLoadGameStart) {
9174               case GNUChessGame:
9175               case XBoardGame:
9176               case PGNTag:
9177                 break;
9178               case MoveNumberOne:
9179               case (ChessMove) 0:
9180                 gn--;           /* count this game */
9181                 lastLoadGameStart = cm;
9182                 break;
9183               default:
9184                 /* impossible */
9185                 break;
9186             }
9187             break;
9188
9189           case PGNTag:
9190             switch (lastLoadGameStart) {
9191               case GNUChessGame:
9192               case PGNTag:
9193               case MoveNumberOne:
9194               case (ChessMove) 0:
9195                 gn--;           /* count this game */
9196                 lastLoadGameStart = cm;
9197                 break;
9198               case XBoardGame:
9199                 lastLoadGameStart = cm; /* game counted already */
9200                 break;
9201               default:
9202                 /* impossible */
9203                 break;
9204             }
9205             if (gn > 0) {
9206                 do {
9207                     yyboardindex = forwardMostMove;
9208                     cm = (ChessMove) yylex();
9209                 } while (cm == PGNTag || cm == Comment);
9210             }
9211             break;
9212
9213           case WhiteWins:
9214           case BlackWins:
9215           case GameIsDrawn:
9216             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9217                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9218                     != CMAIL_OLD_RESULT) {
9219                     nCmailResults ++ ;
9220                     cmailResult[  CMAIL_MAX_GAMES
9221                                 - gn - 1] = CMAIL_OLD_RESULT;
9222                 }
9223             }
9224             break;
9225
9226           case NormalMove:
9227             /* Only a NormalMove can be at the start of a game
9228              * without a position diagram. */
9229             if (lastLoadGameStart == (ChessMove) 0) {
9230               gn--;
9231               lastLoadGameStart = MoveNumberOne;
9232             }
9233             break;
9234
9235           default:
9236             break;
9237         }
9238     }
9239     
9240     if (appData.debugMode)
9241       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9242
9243     if (cm == XBoardGame) {
9244         /* Skip any header junk before position diagram and/or move 1 */
9245         for (;;) {
9246             yyboardindex = forwardMostMove;
9247             cm = (ChessMove) yylex();
9248
9249             if (cm == (ChessMove) 0 ||
9250                 cm == GNUChessGame || cm == XBoardGame) {
9251                 /* Empty game; pretend end-of-file and handle later */
9252                 cm = (ChessMove) 0;
9253                 break;
9254             }
9255
9256             if (cm == MoveNumberOne || cm == PositionDiagram ||
9257                 cm == PGNTag || cm == Comment)
9258               break;
9259         }
9260     } else if (cm == GNUChessGame) {
9261         if (gameInfo.event != NULL) {
9262             free(gameInfo.event);
9263         }
9264         gameInfo.event = StrSave(yy_text);
9265     }   
9266
9267     startedFromSetupPosition = FALSE;
9268     while (cm == PGNTag) {
9269         if (appData.debugMode) 
9270           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9271         err = ParsePGNTag(yy_text, &gameInfo);
9272         if (!err) numPGNTags++;
9273
9274         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9275         if(gameInfo.variant != oldVariant) {
9276             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9277             InitPosition(TRUE);
9278             oldVariant = gameInfo.variant;
9279             if (appData.debugMode) 
9280               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9281         }
9282
9283
9284         if (gameInfo.fen != NULL) {
9285           Board initial_position;
9286           startedFromSetupPosition = TRUE;
9287           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9288             Reset(TRUE, TRUE);
9289             DisplayError(_("Bad FEN position in file"), 0);
9290             return FALSE;
9291           }
9292           CopyBoard(boards[0], initial_position);
9293           if (blackPlaysFirst) {
9294             currentMove = forwardMostMove = backwardMostMove = 1;
9295             CopyBoard(boards[1], initial_position);
9296             strcpy(moveList[0], "");
9297             strcpy(parseList[0], "");
9298             timeRemaining[0][1] = whiteTimeRemaining;
9299             timeRemaining[1][1] = blackTimeRemaining;
9300             if (commentList[0] != NULL) {
9301               commentList[1] = commentList[0];
9302               commentList[0] = NULL;
9303             }
9304           } else {
9305             currentMove = forwardMostMove = backwardMostMove = 0;
9306           }
9307           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9308           {   int i;
9309               initialRulePlies = FENrulePlies;
9310               epStatus[forwardMostMove] = FENepStatus;
9311               for( i=0; i< nrCastlingRights; i++ )
9312                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9313           }
9314           yyboardindex = forwardMostMove;
9315           free(gameInfo.fen);
9316           gameInfo.fen = NULL;
9317         }
9318
9319         yyboardindex = forwardMostMove;
9320         cm = (ChessMove) yylex();
9321
9322         /* Handle comments interspersed among the tags */
9323         while (cm == Comment) {
9324             char *p;
9325             if (appData.debugMode) 
9326               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9327             p = yy_text;
9328             if (*p == '{' || *p == '[' || *p == '(') {
9329                 p[strlen(p) - 1] = NULLCHAR;
9330                 p++;
9331             }
9332             while (*p == '\n') p++;
9333             AppendComment(currentMove, p);
9334             yyboardindex = forwardMostMove;
9335             cm = (ChessMove) yylex();
9336         }
9337     }
9338
9339     /* don't rely on existence of Event tag since if game was
9340      * pasted from clipboard the Event tag may not exist
9341      */
9342     if (numPGNTags > 0){
9343         char *tags;
9344         if (gameInfo.variant == VariantNormal) {
9345           gameInfo.variant = StringToVariant(gameInfo.event);
9346         }
9347         if (!matchMode) {
9348           if( appData.autoDisplayTags ) {
9349             tags = PGNTags(&gameInfo);
9350             TagsPopUp(tags, CmailMsg());
9351             free(tags);
9352           }
9353         }
9354     } else {
9355         /* Make something up, but don't display it now */
9356         SetGameInfo();
9357         TagsPopDown();
9358     }
9359
9360     if (cm == PositionDiagram) {
9361         int i, j;
9362         char *p;
9363         Board initial_position;
9364
9365         if (appData.debugMode)
9366           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9367
9368         if (!startedFromSetupPosition) {
9369             p = yy_text;
9370             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9371               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9372                 switch (*p) {
9373                   case '[':
9374                   case '-':
9375                   case ' ':
9376                   case '\t':
9377                   case '\n':
9378                   case '\r':
9379                     break;
9380                   default:
9381                     initial_position[i][j++] = CharToPiece(*p);
9382                     break;
9383                 }
9384             while (*p == ' ' || *p == '\t' ||
9385                    *p == '\n' || *p == '\r') p++;
9386         
9387             if (strncmp(p, "black", strlen("black"))==0)
9388               blackPlaysFirst = TRUE;
9389             else
9390               blackPlaysFirst = FALSE;
9391             startedFromSetupPosition = TRUE;
9392         
9393             CopyBoard(boards[0], initial_position);
9394             if (blackPlaysFirst) {
9395                 currentMove = forwardMostMove = backwardMostMove = 1;
9396                 CopyBoard(boards[1], initial_position);
9397                 strcpy(moveList[0], "");
9398                 strcpy(parseList[0], "");
9399                 timeRemaining[0][1] = whiteTimeRemaining;
9400                 timeRemaining[1][1] = blackTimeRemaining;
9401                 if (commentList[0] != NULL) {
9402                     commentList[1] = commentList[0];
9403                     commentList[0] = NULL;
9404                 }
9405             } else {
9406                 currentMove = forwardMostMove = backwardMostMove = 0;
9407             }
9408         }
9409         yyboardindex = forwardMostMove;
9410         cm = (ChessMove) yylex();
9411     }
9412
9413     if (first.pr == NoProc) {
9414         StartChessProgram(&first);
9415     }
9416     InitChessProgram(&first, FALSE);
9417     SendToProgram("force\n", &first);
9418     if (startedFromSetupPosition) {
9419         SendBoard(&first, forwardMostMove);
9420     if (appData.debugMode) {
9421         fprintf(debugFP, "Load Game\n");
9422     }
9423         DisplayBothClocks();
9424     }      
9425
9426     /* [HGM] server: flag to write setup moves in broadcast file as one */
9427     loadFlag = appData.suppressLoadMoves;
9428
9429     while (cm == Comment) {
9430         char *p;
9431         if (appData.debugMode) 
9432           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9433         p = yy_text;
9434         if (*p == '{' || *p == '[' || *p == '(') {
9435             p[strlen(p) - 1] = NULLCHAR;
9436             p++;
9437         }
9438         while (*p == '\n') p++;
9439         AppendComment(currentMove, p);
9440         yyboardindex = forwardMostMove;
9441         cm = (ChessMove) yylex();
9442     }
9443
9444     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9445         cm == WhiteWins || cm == BlackWins ||
9446         cm == GameIsDrawn || cm == GameUnfinished) {
9447         DisplayMessage("", _("No moves in game"));
9448         if (cmailMsgLoaded) {
9449             if (appData.debugMode)
9450               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9451             ClearHighlights();
9452             flipView = FALSE;
9453         }
9454         DrawPosition(FALSE, boards[currentMove]);
9455         DisplayBothClocks();
9456         gameMode = EditGame;
9457         ModeHighlight();
9458         gameFileFP = NULL;
9459         cmailOldMove = 0;
9460         return TRUE;
9461     }
9462
9463     // [HGM] PV info: routine tests if comment empty
9464     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9465         DisplayComment(currentMove - 1, commentList[currentMove]);
9466     }
9467     if (!matchMode && appData.timeDelay != 0) 
9468       DrawPosition(FALSE, boards[currentMove]);
9469
9470     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9471       programStats.ok_to_send = 1;
9472     }
9473
9474     /* if the first token after the PGN tags is a move
9475      * and not move number 1, retrieve it from the parser 
9476      */
9477     if (cm != MoveNumberOne)
9478         LoadGameOneMove(cm);
9479
9480     /* load the remaining moves from the file */
9481     while (LoadGameOneMove((ChessMove)0)) {
9482       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9483       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9484     }
9485
9486     /* rewind to the start of the game */
9487     currentMove = backwardMostMove;
9488
9489     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9490
9491     if (oldGameMode == AnalyzeFile ||
9492         oldGameMode == AnalyzeMode) {
9493       AnalyzeFileEvent();
9494     }
9495
9496     if (matchMode || appData.timeDelay == 0) {
9497       ToEndEvent();
9498       gameMode = EditGame;
9499       ModeHighlight();
9500     } else if (appData.timeDelay > 0) {
9501       AutoPlayGameLoop();
9502     }
9503
9504     if (appData.debugMode) 
9505         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9506
9507     loadFlag = 0; /* [HGM] true game starts */
9508     return TRUE;
9509 }
9510
9511 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9512 int
9513 ReloadPosition(offset)
9514      int offset;
9515 {
9516     int positionNumber = lastLoadPositionNumber + offset;
9517     if (lastLoadPositionFP == NULL) {
9518         DisplayError(_("No position has been loaded yet"), 0);
9519         return FALSE;
9520     }
9521     if (positionNumber <= 0) {
9522         DisplayError(_("Can't back up any further"), 0);
9523         return FALSE;
9524     }
9525     return LoadPosition(lastLoadPositionFP, positionNumber,
9526                         lastLoadPositionTitle);
9527 }
9528
9529 /* Load the nth position from the given file */
9530 int
9531 LoadPositionFromFile(filename, n, title)
9532      char *filename;
9533      int n;
9534      char *title;
9535 {
9536     FILE *f;
9537     char buf[MSG_SIZ];
9538
9539     if (strcmp(filename, "-") == 0) {
9540         return LoadPosition(stdin, n, "stdin");
9541     } else {
9542         f = fopen(filename, "rb");
9543         if (f == NULL) {
9544             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9545             DisplayError(buf, errno);
9546             return FALSE;
9547         } else {
9548             return LoadPosition(f, n, title);
9549         }
9550     }
9551 }
9552
9553 /* Load the nth position from the given open file, and close it */
9554 int
9555 LoadPosition(f, positionNumber, title)
9556      FILE *f;
9557      int positionNumber;
9558      char *title;
9559 {
9560     char *p, line[MSG_SIZ];
9561     Board initial_position;
9562     int i, j, fenMode, pn;
9563     
9564     if (gameMode == Training )
9565         SetTrainingModeOff();
9566
9567     if (gameMode != BeginningOfGame) {
9568         Reset(FALSE, TRUE);
9569     }
9570     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9571         fclose(lastLoadPositionFP);
9572     }
9573     if (positionNumber == 0) positionNumber = 1;
9574     lastLoadPositionFP = f;
9575     lastLoadPositionNumber = positionNumber;
9576     strcpy(lastLoadPositionTitle, title);
9577     if (first.pr == NoProc) {
9578       StartChessProgram(&first);
9579       InitChessProgram(&first, FALSE);
9580     }    
9581     pn = positionNumber;
9582     if (positionNumber < 0) {
9583         /* Negative position number means to seek to that byte offset */
9584         if (fseek(f, -positionNumber, 0) == -1) {
9585             DisplayError(_("Can't seek on position file"), 0);
9586             return FALSE;
9587         };
9588         pn = 1;
9589     } else {
9590         if (fseek(f, 0, 0) == -1) {
9591             if (f == lastLoadPositionFP ?
9592                 positionNumber == lastLoadPositionNumber + 1 :
9593                 positionNumber == 1) {
9594                 pn = 1;
9595             } else {
9596                 DisplayError(_("Can't seek on position file"), 0);
9597                 return FALSE;
9598             }
9599         }
9600     }
9601     /* See if this file is FEN or old-style xboard */
9602     if (fgets(line, MSG_SIZ, f) == NULL) {
9603         DisplayError(_("Position not found in file"), 0);
9604         return FALSE;
9605     }
9606     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9607     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9608
9609     if (pn >= 2) {
9610         if (fenMode || line[0] == '#') pn--;
9611         while (pn > 0) {
9612             /* skip positions before number pn */
9613             if (fgets(line, MSG_SIZ, f) == NULL) {
9614                 Reset(TRUE, TRUE);
9615                 DisplayError(_("Position not found in file"), 0);
9616                 return FALSE;
9617             }
9618             if (fenMode || line[0] == '#') pn--;
9619         }
9620     }
9621
9622     if (fenMode) {
9623         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9624             DisplayError(_("Bad FEN position in file"), 0);
9625             return FALSE;
9626         }
9627     } else {
9628         (void) fgets(line, MSG_SIZ, f);
9629         (void) fgets(line, MSG_SIZ, f);
9630     
9631         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9632             (void) fgets(line, MSG_SIZ, f);
9633             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9634                 if (*p == ' ')
9635                   continue;
9636                 initial_position[i][j++] = CharToPiece(*p);
9637             }
9638         }
9639     
9640         blackPlaysFirst = FALSE;
9641         if (!feof(f)) {
9642             (void) fgets(line, MSG_SIZ, f);
9643             if (strncmp(line, "black", strlen("black"))==0)
9644               blackPlaysFirst = TRUE;
9645         }
9646     }
9647     startedFromSetupPosition = TRUE;
9648     
9649     SendToProgram("force\n", &first);
9650     CopyBoard(boards[0], initial_position);
9651     if (blackPlaysFirst) {
9652         currentMove = forwardMostMove = backwardMostMove = 1;
9653         strcpy(moveList[0], "");
9654         strcpy(parseList[0], "");
9655         CopyBoard(boards[1], initial_position);
9656         DisplayMessage("", _("Black to play"));
9657     } else {
9658         currentMove = forwardMostMove = backwardMostMove = 0;
9659         DisplayMessage("", _("White to play"));
9660     }
9661           /* [HGM] copy FEN attributes as well */
9662           {   int i;
9663               initialRulePlies = FENrulePlies;
9664               epStatus[forwardMostMove] = FENepStatus;
9665               for( i=0; i< nrCastlingRights; i++ )
9666                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9667           }
9668     SendBoard(&first, forwardMostMove);
9669     if (appData.debugMode) {
9670 int i, j;
9671   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9672   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9673         fprintf(debugFP, "Load Position\n");
9674     }
9675
9676     if (positionNumber > 1) {
9677         sprintf(line, "%s %d", title, positionNumber);
9678         DisplayTitle(line);
9679     } else {
9680         DisplayTitle(title);
9681     }
9682     gameMode = EditGame;
9683     ModeHighlight();
9684     ResetClocks();
9685     timeRemaining[0][1] = whiteTimeRemaining;
9686     timeRemaining[1][1] = blackTimeRemaining;
9687     DrawPosition(FALSE, boards[currentMove]);
9688    
9689     return TRUE;
9690 }
9691
9692
9693 void
9694 CopyPlayerNameIntoFileName(dest, src)
9695      char **dest, *src;
9696 {
9697     while (*src != NULLCHAR && *src != ',') {
9698         if (*src == ' ') {
9699             *(*dest)++ = '_';
9700             src++;
9701         } else {
9702             *(*dest)++ = *src++;
9703         }
9704     }
9705 }
9706
9707 char *DefaultFileName(ext)
9708      char *ext;
9709 {
9710     static char def[MSG_SIZ];
9711     char *p;
9712
9713     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9714         p = def;
9715         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9716         *p++ = '-';
9717         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9718         *p++ = '.';
9719         strcpy(p, ext);
9720     } else {
9721         def[0] = NULLCHAR;
9722     }
9723     return def;
9724 }
9725
9726 /* Save the current game to the given file */
9727 int
9728 SaveGameToFile(filename, append)
9729      char *filename;
9730      int append;
9731 {
9732     FILE *f;
9733     char buf[MSG_SIZ];
9734
9735     if (strcmp(filename, "-") == 0) {
9736         return SaveGame(stdout, 0, NULL);
9737     } else {
9738         f = fopen(filename, append ? "a" : "w");
9739         if (f == NULL) {
9740             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9741             DisplayError(buf, errno);
9742             return FALSE;
9743         } else {
9744             return SaveGame(f, 0, NULL);
9745         }
9746     }
9747 }
9748
9749 char *
9750 SavePart(str)
9751      char *str;
9752 {
9753     static char buf[MSG_SIZ];
9754     char *p;
9755     
9756     p = strchr(str, ' ');
9757     if (p == NULL) return str;
9758     strncpy(buf, str, p - str);
9759     buf[p - str] = NULLCHAR;
9760     return buf;
9761 }
9762
9763 #define PGN_MAX_LINE 75
9764
9765 #define PGN_SIDE_WHITE  0
9766 #define PGN_SIDE_BLACK  1
9767
9768 /* [AS] */
9769 static int FindFirstMoveOutOfBook( int side )
9770 {
9771     int result = -1;
9772
9773     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9774         int index = backwardMostMove;
9775         int has_book_hit = 0;
9776
9777         if( (index % 2) != side ) {
9778             index++;
9779         }
9780
9781         while( index < forwardMostMove ) {
9782             /* Check to see if engine is in book */
9783             int depth = pvInfoList[index].depth;
9784             int score = pvInfoList[index].score;
9785             int in_book = 0;
9786
9787             if( depth <= 2 ) {
9788                 in_book = 1;
9789             }
9790             else if( score == 0 && depth == 63 ) {
9791                 in_book = 1; /* Zappa */
9792             }
9793             else if( score == 2 && depth == 99 ) {
9794                 in_book = 1; /* Abrok */
9795             }
9796
9797             has_book_hit += in_book;
9798
9799             if( ! in_book ) {
9800                 result = index;
9801
9802                 break;
9803             }
9804
9805             index += 2;
9806         }
9807     }
9808
9809     return result;
9810 }
9811
9812 /* [AS] */
9813 void GetOutOfBookInfo( char * buf )
9814 {
9815     int oob[2];
9816     int i;
9817     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9818
9819     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9820     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9821
9822     *buf = '\0';
9823
9824     if( oob[0] >= 0 || oob[1] >= 0 ) {
9825         for( i=0; i<2; i++ ) {
9826             int idx = oob[i];
9827
9828             if( idx >= 0 ) {
9829                 if( i > 0 && oob[0] >= 0 ) {
9830                     strcat( buf, "   " );
9831                 }
9832
9833                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9834                 sprintf( buf+strlen(buf), "%s%.2f", 
9835                     pvInfoList[idx].score >= 0 ? "+" : "",
9836                     pvInfoList[idx].score / 100.0 );
9837             }
9838         }
9839     }
9840 }
9841
9842 /* Save game in PGN style and close the file */
9843 int
9844 SaveGamePGN(f)
9845      FILE *f;
9846 {
9847     int i, offset, linelen, newblock;
9848     time_t tm;
9849 //    char *movetext;
9850     char numtext[32];
9851     int movelen, numlen, blank;
9852     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9853
9854     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9855     
9856     tm = time((time_t *) NULL);
9857     
9858     PrintPGNTags(f, &gameInfo);
9859     
9860     if (backwardMostMove > 0 || startedFromSetupPosition) {
9861         char *fen = PositionToFEN(backwardMostMove, NULL);
9862         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9863         fprintf(f, "\n{--------------\n");
9864         PrintPosition(f, backwardMostMove);
9865         fprintf(f, "--------------}\n");
9866         free(fen);
9867     }
9868     else {
9869         /* [AS] Out of book annotation */
9870         if( appData.saveOutOfBookInfo ) {
9871             char buf[64];
9872
9873             GetOutOfBookInfo( buf );
9874
9875             if( buf[0] != '\0' ) {
9876                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9877             }
9878         }
9879
9880         fprintf(f, "\n");
9881     }
9882
9883     i = backwardMostMove;
9884     linelen = 0;
9885     newblock = TRUE;
9886
9887     while (i < forwardMostMove) {
9888         /* Print comments preceding this move */
9889         if (commentList[i] != NULL) {
9890             if (linelen > 0) fprintf(f, "\n");
9891             fprintf(f, "{\n%s}\n", commentList[i]);
9892             linelen = 0;
9893             newblock = TRUE;
9894         }
9895
9896         /* Format move number */
9897         if ((i % 2) == 0) {
9898             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9899         } else {
9900             if (newblock) {
9901                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9902             } else {
9903                 numtext[0] = NULLCHAR;
9904             }
9905         }
9906         numlen = strlen(numtext);
9907         newblock = FALSE;
9908
9909         /* Print move number */
9910         blank = linelen > 0 && numlen > 0;
9911         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9912             fprintf(f, "\n");
9913             linelen = 0;
9914             blank = 0;
9915         }
9916         if (blank) {
9917             fprintf(f, " ");
9918             linelen++;
9919         }
9920         fprintf(f, "%s", numtext);
9921         linelen += numlen;
9922
9923         /* Get move */
9924         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9925         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9926
9927         /* Print move */
9928         blank = linelen > 0 && movelen > 0;
9929         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9930             fprintf(f, "\n");
9931             linelen = 0;
9932             blank = 0;
9933         }
9934         if (blank) {
9935             fprintf(f, " ");
9936             linelen++;
9937         }
9938         fprintf(f, "%s", move_buffer);
9939         linelen += movelen;
9940
9941         /* [AS] Add PV info if present */
9942         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9943             /* [HGM] add time */
9944             char buf[MSG_SIZ]; int seconds;
9945
9946             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9947
9948             if( seconds <= 0) buf[0] = 0; else
9949             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9950                 seconds = (seconds + 4)/10; // round to full seconds
9951                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9952                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9953             }
9954
9955             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9956                 pvInfoList[i].score >= 0 ? "+" : "",
9957                 pvInfoList[i].score / 100.0,
9958                 pvInfoList[i].depth,
9959                 buf );
9960
9961             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9962
9963             /* Print score/depth */
9964             blank = linelen > 0 && movelen > 0;
9965             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9966                 fprintf(f, "\n");
9967                 linelen = 0;
9968                 blank = 0;
9969             }
9970             if (blank) {
9971                 fprintf(f, " ");
9972                 linelen++;
9973             }
9974             fprintf(f, "%s", move_buffer);
9975             linelen += movelen;
9976         }
9977
9978         i++;
9979     }
9980     
9981     /* Start a new line */
9982     if (linelen > 0) fprintf(f, "\n");
9983
9984     /* Print comments after last move */
9985     if (commentList[i] != NULL) {
9986         fprintf(f, "{\n%s}\n", commentList[i]);
9987     }
9988
9989     /* Print result */
9990     if (gameInfo.resultDetails != NULL &&
9991         gameInfo.resultDetails[0] != NULLCHAR) {
9992         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9993                 PGNResult(gameInfo.result));
9994     } else {
9995         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9996     }
9997
9998     fclose(f);
9999     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10000     return TRUE;
10001 }
10002
10003 /* Save game in old style and close the file */
10004 int
10005 SaveGameOldStyle(f)
10006      FILE *f;
10007 {
10008     int i, offset;
10009     time_t tm;
10010     
10011     tm = time((time_t *) NULL);
10012     
10013     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10014     PrintOpponents(f);
10015     
10016     if (backwardMostMove > 0 || startedFromSetupPosition) {
10017         fprintf(f, "\n[--------------\n");
10018         PrintPosition(f, backwardMostMove);
10019         fprintf(f, "--------------]\n");
10020     } else {
10021         fprintf(f, "\n");
10022     }
10023
10024     i = backwardMostMove;
10025     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10026
10027     while (i < forwardMostMove) {
10028         if (commentList[i] != NULL) {
10029             fprintf(f, "[%s]\n", commentList[i]);
10030         }
10031
10032         if ((i % 2) == 1) {
10033             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10034             i++;
10035         } else {
10036             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10037             i++;
10038             if (commentList[i] != NULL) {
10039                 fprintf(f, "\n");
10040                 continue;
10041             }
10042             if (i >= forwardMostMove) {
10043                 fprintf(f, "\n");
10044                 break;
10045             }
10046             fprintf(f, "%s\n", parseList[i]);
10047             i++;
10048         }
10049     }
10050     
10051     if (commentList[i] != NULL) {
10052         fprintf(f, "[%s]\n", commentList[i]);
10053     }
10054
10055     /* This isn't really the old style, but it's close enough */
10056     if (gameInfo.resultDetails != NULL &&
10057         gameInfo.resultDetails[0] != NULLCHAR) {
10058         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10059                 gameInfo.resultDetails);
10060     } else {
10061         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10062     }
10063
10064     fclose(f);
10065     return TRUE;
10066 }
10067
10068 /* Save the current game to open file f and close the file */
10069 int
10070 SaveGame(f, dummy, dummy2)
10071      FILE *f;
10072      int dummy;
10073      char *dummy2;
10074 {
10075     if (gameMode == EditPosition) EditPositionDone(TRUE);
10076     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10077     if (appData.oldSaveStyle)
10078       return SaveGameOldStyle(f);
10079     else
10080       return SaveGamePGN(f);
10081 }
10082
10083 /* Save the current position to the given file */
10084 int
10085 SavePositionToFile(filename)
10086      char *filename;
10087 {
10088     FILE *f;
10089     char buf[MSG_SIZ];
10090
10091     if (strcmp(filename, "-") == 0) {
10092         return SavePosition(stdout, 0, NULL);
10093     } else {
10094         f = fopen(filename, "a");
10095         if (f == NULL) {
10096             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10097             DisplayError(buf, errno);
10098             return FALSE;
10099         } else {
10100             SavePosition(f, 0, NULL);
10101             return TRUE;
10102         }
10103     }
10104 }
10105
10106 /* Save the current position to the given open file and close the file */
10107 int
10108 SavePosition(f, dummy, dummy2)
10109      FILE *f;
10110      int dummy;
10111      char *dummy2;
10112 {
10113     time_t tm;
10114     char *fen;
10115
10116     if (gameMode == EditPosition) EditPositionDone(TRUE);
10117     if (appData.oldSaveStyle) {
10118         tm = time((time_t *) NULL);
10119     
10120         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10121         PrintOpponents(f);
10122         fprintf(f, "[--------------\n");
10123         PrintPosition(f, currentMove);
10124         fprintf(f, "--------------]\n");
10125     } else {
10126         fen = PositionToFEN(currentMove, NULL);
10127         fprintf(f, "%s\n", fen);
10128         free(fen);
10129     }
10130     fclose(f);
10131     return TRUE;
10132 }
10133
10134 void
10135 ReloadCmailMsgEvent(unregister)
10136      int unregister;
10137 {
10138 #if !WIN32
10139     static char *inFilename = NULL;
10140     static char *outFilename;
10141     int i;
10142     struct stat inbuf, outbuf;
10143     int status;
10144     
10145     /* Any registered moves are unregistered if unregister is set, */
10146     /* i.e. invoked by the signal handler */
10147     if (unregister) {
10148         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10149             cmailMoveRegistered[i] = FALSE;
10150             if (cmailCommentList[i] != NULL) {
10151                 free(cmailCommentList[i]);
10152                 cmailCommentList[i] = NULL;
10153             }
10154         }
10155         nCmailMovesRegistered = 0;
10156     }
10157
10158     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10159         cmailResult[i] = CMAIL_NOT_RESULT;
10160     }
10161     nCmailResults = 0;
10162
10163     if (inFilename == NULL) {
10164         /* Because the filenames are static they only get malloced once  */
10165         /* and they never get freed                                      */
10166         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10167         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10168
10169         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10170         sprintf(outFilename, "%s.out", appData.cmailGameName);
10171     }
10172     
10173     status = stat(outFilename, &outbuf);
10174     if (status < 0) {
10175         cmailMailedMove = FALSE;
10176     } else {
10177         status = stat(inFilename, &inbuf);
10178         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10179     }
10180     
10181     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10182        counts the games, notes how each one terminated, etc.
10183        
10184        It would be nice to remove this kludge and instead gather all
10185        the information while building the game list.  (And to keep it
10186        in the game list nodes instead of having a bunch of fixed-size
10187        parallel arrays.)  Note this will require getting each game's
10188        termination from the PGN tags, as the game list builder does
10189        not process the game moves.  --mann
10190        */
10191     cmailMsgLoaded = TRUE;
10192     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10193     
10194     /* Load first game in the file or popup game menu */
10195     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10196
10197 #endif /* !WIN32 */
10198     return;
10199 }
10200
10201 int
10202 RegisterMove()
10203 {
10204     FILE *f;
10205     char string[MSG_SIZ];
10206
10207     if (   cmailMailedMove
10208         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10209         return TRUE;            /* Allow free viewing  */
10210     }
10211
10212     /* Unregister move to ensure that we don't leave RegisterMove        */
10213     /* with the move registered when the conditions for registering no   */
10214     /* longer hold                                                       */
10215     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10216         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10217         nCmailMovesRegistered --;
10218
10219         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10220           {
10221               free(cmailCommentList[lastLoadGameNumber - 1]);
10222               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10223           }
10224     }
10225
10226     if (cmailOldMove == -1) {
10227         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10228         return FALSE;
10229     }
10230
10231     if (currentMove > cmailOldMove + 1) {
10232         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10233         return FALSE;
10234     }
10235
10236     if (currentMove < cmailOldMove) {
10237         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10238         return FALSE;
10239     }
10240
10241     if (forwardMostMove > currentMove) {
10242         /* Silently truncate extra moves */
10243         TruncateGame();
10244     }
10245
10246     if (   (currentMove == cmailOldMove + 1)
10247         || (   (currentMove == cmailOldMove)
10248             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10249                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10250         if (gameInfo.result != GameUnfinished) {
10251             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10252         }
10253
10254         if (commentList[currentMove] != NULL) {
10255             cmailCommentList[lastLoadGameNumber - 1]
10256               = StrSave(commentList[currentMove]);
10257         }
10258         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10259
10260         if (appData.debugMode)
10261           fprintf(debugFP, "Saving %s for game %d\n",
10262                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10263
10264         sprintf(string,
10265                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10266         
10267         f = fopen(string, "w");
10268         if (appData.oldSaveStyle) {
10269             SaveGameOldStyle(f); /* also closes the file */
10270             
10271             sprintf(string, "%s.pos.out", appData.cmailGameName);
10272             f = fopen(string, "w");
10273             SavePosition(f, 0, NULL); /* also closes the file */
10274         } else {
10275             fprintf(f, "{--------------\n");
10276             PrintPosition(f, currentMove);
10277             fprintf(f, "--------------}\n\n");
10278             
10279             SaveGame(f, 0, NULL); /* also closes the file*/
10280         }
10281         
10282         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10283         nCmailMovesRegistered ++;
10284     } else if (nCmailGames == 1) {
10285         DisplayError(_("You have not made a move yet"), 0);
10286         return FALSE;
10287     }
10288
10289     return TRUE;
10290 }
10291
10292 void
10293 MailMoveEvent()
10294 {
10295 #if !WIN32
10296     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10297     FILE *commandOutput;
10298     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10299     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10300     int nBuffers;
10301     int i;
10302     int archived;
10303     char *arcDir;
10304
10305     if (! cmailMsgLoaded) {
10306         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10307         return;
10308     }
10309
10310     if (nCmailGames == nCmailResults) {
10311         DisplayError(_("No unfinished games"), 0);
10312         return;
10313     }
10314
10315 #if CMAIL_PROHIBIT_REMAIL
10316     if (cmailMailedMove) {
10317         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);
10318         DisplayError(msg, 0);
10319         return;
10320     }
10321 #endif
10322
10323     if (! (cmailMailedMove || RegisterMove())) return;
10324     
10325     if (   cmailMailedMove
10326         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10327         sprintf(string, partCommandString,
10328                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10329         commandOutput = popen(string, "r");
10330
10331         if (commandOutput == NULL) {
10332             DisplayError(_("Failed to invoke cmail"), 0);
10333         } else {
10334             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10335                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10336             }
10337             if (nBuffers > 1) {
10338                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10339                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10340                 nBytes = MSG_SIZ - 1;
10341             } else {
10342                 (void) memcpy(msg, buffer, nBytes);
10343             }
10344             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10345
10346             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10347                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10348
10349                 archived = TRUE;
10350                 for (i = 0; i < nCmailGames; i ++) {
10351                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10352                         archived = FALSE;
10353                     }
10354                 }
10355                 if (   archived
10356                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10357                         != NULL)) {
10358                     sprintf(buffer, "%s/%s.%s.archive",
10359                             arcDir,
10360                             appData.cmailGameName,
10361                             gameInfo.date);
10362                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10363                     cmailMsgLoaded = FALSE;
10364                 }
10365             }
10366
10367             DisplayInformation(msg);
10368             pclose(commandOutput);
10369         }
10370     } else {
10371         if ((*cmailMsg) != '\0') {
10372             DisplayInformation(cmailMsg);
10373         }
10374     }
10375
10376     return;
10377 #endif /* !WIN32 */
10378 }
10379
10380 char *
10381 CmailMsg()
10382 {
10383 #if WIN32
10384     return NULL;
10385 #else
10386     int  prependComma = 0;
10387     char number[5];
10388     char string[MSG_SIZ];       /* Space for game-list */
10389     int  i;
10390     
10391     if (!cmailMsgLoaded) return "";
10392
10393     if (cmailMailedMove) {
10394         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10395     } else {
10396         /* Create a list of games left */
10397         sprintf(string, "[");
10398         for (i = 0; i < nCmailGames; i ++) {
10399             if (! (   cmailMoveRegistered[i]
10400                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10401                 if (prependComma) {
10402                     sprintf(number, ",%d", i + 1);
10403                 } else {
10404                     sprintf(number, "%d", i + 1);
10405                     prependComma = 1;
10406                 }
10407                 
10408                 strcat(string, number);
10409             }
10410         }
10411         strcat(string, "]");
10412
10413         if (nCmailMovesRegistered + nCmailResults == 0) {
10414             switch (nCmailGames) {
10415               case 1:
10416                 sprintf(cmailMsg,
10417                         _("Still need to make move for game\n"));
10418                 break;
10419                 
10420               case 2:
10421                 sprintf(cmailMsg,
10422                         _("Still need to make moves for both games\n"));
10423                 break;
10424                 
10425               default:
10426                 sprintf(cmailMsg,
10427                         _("Still need to make moves for all %d games\n"),
10428                         nCmailGames);
10429                 break;
10430             }
10431         } else {
10432             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10433               case 1:
10434                 sprintf(cmailMsg,
10435                         _("Still need to make a move for game %s\n"),
10436                         string);
10437                 break;
10438                 
10439               case 0:
10440                 if (nCmailResults == nCmailGames) {
10441                     sprintf(cmailMsg, _("No unfinished games\n"));
10442                 } else {
10443                     sprintf(cmailMsg, _("Ready to send mail\n"));
10444                 }
10445                 break;
10446                 
10447               default:
10448                 sprintf(cmailMsg,
10449                         _("Still need to make moves for games %s\n"),
10450                         string);
10451             }
10452         }
10453     }
10454     return cmailMsg;
10455 #endif /* WIN32 */
10456 }
10457
10458 void
10459 ResetGameEvent()
10460 {
10461     if (gameMode == Training)
10462       SetTrainingModeOff();
10463
10464     Reset(TRUE, TRUE);
10465     cmailMsgLoaded = FALSE;
10466     if (appData.icsActive) {
10467       SendToICS(ics_prefix);
10468       SendToICS("refresh\n");
10469     }
10470 }
10471
10472 void
10473 ExitEvent(status)
10474      int status;
10475 {
10476     exiting++;
10477     if (exiting > 2) {
10478       /* Give up on clean exit */
10479       exit(status);
10480     }
10481     if (exiting > 1) {
10482       /* Keep trying for clean exit */
10483       return;
10484     }
10485
10486     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10487
10488     if (telnetISR != NULL) {
10489       RemoveInputSource(telnetISR);
10490     }
10491     if (icsPR != NoProc) {
10492       DestroyChildProcess(icsPR, TRUE);
10493     }
10494
10495     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10496     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10497
10498     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10499     /* make sure this other one finishes before killing it!                  */
10500     if(endingGame) { int count = 0;
10501         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10502         while(endingGame && count++ < 10) DoSleep(1);
10503         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10504     }
10505
10506     /* Kill off chess programs */
10507     if (first.pr != NoProc) {
10508         ExitAnalyzeMode();
10509         
10510         DoSleep( appData.delayBeforeQuit );
10511         SendToProgram("quit\n", &first);
10512         DoSleep( appData.delayAfterQuit );
10513         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10514     }
10515     if (second.pr != NoProc) {
10516         DoSleep( appData.delayBeforeQuit );
10517         SendToProgram("quit\n", &second);
10518         DoSleep( appData.delayAfterQuit );
10519         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10520     }
10521     if (first.isr != NULL) {
10522         RemoveInputSource(first.isr);
10523     }
10524     if (second.isr != NULL) {
10525         RemoveInputSource(second.isr);
10526     }
10527
10528     ShutDownFrontEnd();
10529     exit(status);
10530 }
10531
10532 void
10533 PauseEvent()
10534 {
10535     if (appData.debugMode)
10536         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10537     if (pausing) {
10538         pausing = FALSE;
10539         ModeHighlight();
10540         if (gameMode == MachinePlaysWhite ||
10541             gameMode == MachinePlaysBlack) {
10542             StartClocks();
10543         } else {
10544             DisplayBothClocks();
10545         }
10546         if (gameMode == PlayFromGameFile) {
10547             if (appData.timeDelay >= 0) 
10548                 AutoPlayGameLoop();
10549         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10550             Reset(FALSE, TRUE);
10551             SendToICS(ics_prefix);
10552             SendToICS("refresh\n");
10553         } else if (currentMove < forwardMostMove) {
10554             ForwardInner(forwardMostMove);
10555         }
10556         pauseExamInvalid = FALSE;
10557     } else {
10558         switch (gameMode) {
10559           default:
10560             return;
10561           case IcsExamining:
10562             pauseExamForwardMostMove = forwardMostMove;
10563             pauseExamInvalid = FALSE;
10564             /* fall through */
10565           case IcsObserving:
10566           case IcsPlayingWhite:
10567           case IcsPlayingBlack:
10568             pausing = TRUE;
10569             ModeHighlight();
10570             return;
10571           case PlayFromGameFile:
10572             (void) StopLoadGameTimer();
10573             pausing = TRUE;
10574             ModeHighlight();
10575             break;
10576           case BeginningOfGame:
10577             if (appData.icsActive) return;
10578             /* else fall through */
10579           case MachinePlaysWhite:
10580           case MachinePlaysBlack:
10581           case TwoMachinesPlay:
10582             if (forwardMostMove == 0)
10583               return;           /* don't pause if no one has moved */
10584             if ((gameMode == MachinePlaysWhite &&
10585                  !WhiteOnMove(forwardMostMove)) ||
10586                 (gameMode == MachinePlaysBlack &&
10587                  WhiteOnMove(forwardMostMove))) {
10588                 StopClocks();
10589             }
10590             pausing = TRUE;
10591             ModeHighlight();
10592             break;
10593         }
10594     }
10595 }
10596
10597 void
10598 EditCommentEvent()
10599 {
10600     char title[MSG_SIZ];
10601
10602     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10603         strcpy(title, _("Edit comment"));
10604     } else {
10605         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10606                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10607                 parseList[currentMove - 1]);
10608     }
10609
10610     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10611 }
10612
10613
10614 void
10615 EditTagsEvent()
10616 {
10617     char *tags = PGNTags(&gameInfo);
10618     EditTagsPopUp(tags);
10619     free(tags);
10620 }
10621
10622 void
10623 AnalyzeModeEvent()
10624 {
10625     if (appData.noChessProgram || gameMode == AnalyzeMode)
10626       return;
10627
10628     if (gameMode != AnalyzeFile) {
10629         if (!appData.icsEngineAnalyze) {
10630                EditGameEvent();
10631                if (gameMode != EditGame) return;
10632         }
10633         ResurrectChessProgram();
10634         SendToProgram("analyze\n", &first);
10635         first.analyzing = TRUE;
10636         /*first.maybeThinking = TRUE;*/
10637         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10638         EngineOutputPopUp();
10639     }
10640     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10641     pausing = FALSE;
10642     ModeHighlight();
10643     SetGameInfo();
10644
10645     StartAnalysisClock();
10646     GetTimeMark(&lastNodeCountTime);
10647     lastNodeCount = 0;
10648 }
10649
10650 void
10651 AnalyzeFileEvent()
10652 {
10653     if (appData.noChessProgram || gameMode == AnalyzeFile)
10654       return;
10655
10656     if (gameMode != AnalyzeMode) {
10657         EditGameEvent();
10658         if (gameMode != EditGame) return;
10659         ResurrectChessProgram();
10660         SendToProgram("analyze\n", &first);
10661         first.analyzing = TRUE;
10662         /*first.maybeThinking = TRUE;*/
10663         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10664         EngineOutputPopUp();
10665     }
10666     gameMode = AnalyzeFile;
10667     pausing = FALSE;
10668     ModeHighlight();
10669     SetGameInfo();
10670
10671     StartAnalysisClock();
10672     GetTimeMark(&lastNodeCountTime);
10673     lastNodeCount = 0;
10674 }
10675
10676 void
10677 MachineWhiteEvent()
10678 {
10679     char buf[MSG_SIZ];
10680     char *bookHit = NULL;
10681
10682     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10683       return;
10684
10685
10686     if (gameMode == PlayFromGameFile || 
10687         gameMode == TwoMachinesPlay  || 
10688         gameMode == Training         || 
10689         gameMode == AnalyzeMode      || 
10690         gameMode == EndOfGame)
10691         EditGameEvent();
10692
10693     if (gameMode == EditPosition) 
10694         EditPositionDone(TRUE);
10695
10696     if (!WhiteOnMove(currentMove)) {
10697         DisplayError(_("It is not White's turn"), 0);
10698         return;
10699     }
10700   
10701     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10702       ExitAnalyzeMode();
10703
10704     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10705         gameMode == AnalyzeFile)
10706         TruncateGame();
10707
10708     ResurrectChessProgram();    /* in case it isn't running */
10709     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10710         gameMode = MachinePlaysWhite;
10711         ResetClocks();
10712     } else
10713     gameMode = MachinePlaysWhite;
10714     pausing = FALSE;
10715     ModeHighlight();
10716     SetGameInfo();
10717     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10718     DisplayTitle(buf);
10719     if (first.sendName) {
10720       sprintf(buf, "name %s\n", gameInfo.black);
10721       SendToProgram(buf, &first);
10722     }
10723     if (first.sendTime) {
10724       if (first.useColors) {
10725         SendToProgram("black\n", &first); /*gnu kludge*/
10726       }
10727       SendTimeRemaining(&first, TRUE);
10728     }
10729     if (first.useColors) {
10730       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10731     }
10732     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10733     SetMachineThinkingEnables();
10734     first.maybeThinking = TRUE;
10735     StartClocks();
10736     firstMove = FALSE;
10737
10738     if (appData.autoFlipView && !flipView) {
10739       flipView = !flipView;
10740       DrawPosition(FALSE, NULL);
10741       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10742     }
10743
10744     if(bookHit) { // [HGM] book: simulate book reply
10745         static char bookMove[MSG_SIZ]; // a bit generous?
10746
10747         programStats.nodes = programStats.depth = programStats.time = 
10748         programStats.score = programStats.got_only_move = 0;
10749         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10750
10751         strcpy(bookMove, "move ");
10752         strcat(bookMove, bookHit);
10753         HandleMachineMove(bookMove, &first);
10754     }
10755 }
10756
10757 void
10758 MachineBlackEvent()
10759 {
10760     char buf[MSG_SIZ];
10761    char *bookHit = NULL;
10762
10763     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10764         return;
10765
10766
10767     if (gameMode == PlayFromGameFile || 
10768         gameMode == TwoMachinesPlay  || 
10769         gameMode == Training         || 
10770         gameMode == AnalyzeMode      || 
10771         gameMode == EndOfGame)
10772         EditGameEvent();
10773
10774     if (gameMode == EditPosition) 
10775         EditPositionDone(TRUE);
10776
10777     if (WhiteOnMove(currentMove)) {
10778         DisplayError(_("It is not Black's turn"), 0);
10779         return;
10780     }
10781     
10782     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10783       ExitAnalyzeMode();
10784
10785     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10786         gameMode == AnalyzeFile)
10787         TruncateGame();
10788
10789     ResurrectChessProgram();    /* in case it isn't running */
10790     gameMode = MachinePlaysBlack;
10791     pausing = FALSE;
10792     ModeHighlight();
10793     SetGameInfo();
10794     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10795     DisplayTitle(buf);
10796     if (first.sendName) {
10797       sprintf(buf, "name %s\n", gameInfo.white);
10798       SendToProgram(buf, &first);
10799     }
10800     if (first.sendTime) {
10801       if (first.useColors) {
10802         SendToProgram("white\n", &first); /*gnu kludge*/
10803       }
10804       SendTimeRemaining(&first, FALSE);
10805     }
10806     if (first.useColors) {
10807       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10808     }
10809     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10810     SetMachineThinkingEnables();
10811     first.maybeThinking = TRUE;
10812     StartClocks();
10813
10814     if (appData.autoFlipView && flipView) {
10815       flipView = !flipView;
10816       DrawPosition(FALSE, NULL);
10817       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10818     }
10819     if(bookHit) { // [HGM] book: simulate book reply
10820         static char bookMove[MSG_SIZ]; // a bit generous?
10821
10822         programStats.nodes = programStats.depth = programStats.time = 
10823         programStats.score = programStats.got_only_move = 0;
10824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10825
10826         strcpy(bookMove, "move ");
10827         strcat(bookMove, bookHit);
10828         HandleMachineMove(bookMove, &first);
10829     }
10830 }
10831
10832
10833 void
10834 DisplayTwoMachinesTitle()
10835 {
10836     char buf[MSG_SIZ];
10837     if (appData.matchGames > 0) {
10838         if (first.twoMachinesColor[0] == 'w') {
10839             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10840                     gameInfo.white, gameInfo.black,
10841                     first.matchWins, second.matchWins,
10842                     matchGame - 1 - (first.matchWins + second.matchWins));
10843         } else {
10844             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10845                     gameInfo.white, gameInfo.black,
10846                     second.matchWins, first.matchWins,
10847                     matchGame - 1 - (first.matchWins + second.matchWins));
10848         }
10849     } else {
10850         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10851     }
10852     DisplayTitle(buf);
10853 }
10854
10855 void
10856 TwoMachinesEvent P((void))
10857 {
10858     int i;
10859     char buf[MSG_SIZ];
10860     ChessProgramState *onmove;
10861     char *bookHit = NULL;
10862     
10863     if (appData.noChessProgram) return;
10864
10865     switch (gameMode) {
10866       case TwoMachinesPlay:
10867         return;
10868       case MachinePlaysWhite:
10869       case MachinePlaysBlack:
10870         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10871             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10872             return;
10873         }
10874         /* fall through */
10875       case BeginningOfGame:
10876       case PlayFromGameFile:
10877       case EndOfGame:
10878         EditGameEvent();
10879         if (gameMode != EditGame) return;
10880         break;
10881       case EditPosition:
10882         EditPositionDone(TRUE);
10883         break;
10884       case AnalyzeMode:
10885       case AnalyzeFile:
10886         ExitAnalyzeMode();
10887         break;
10888       case EditGame:
10889       default:
10890         break;
10891     }
10892
10893     forwardMostMove = currentMove;
10894     ResurrectChessProgram();    /* in case first program isn't running */
10895
10896     if (second.pr == NULL) {
10897         StartChessProgram(&second);
10898         if (second.protocolVersion == 1) {
10899           TwoMachinesEventIfReady();
10900         } else {
10901           /* kludge: allow timeout for initial "feature" command */
10902           FreezeUI();
10903           DisplayMessage("", _("Starting second chess program"));
10904           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10905         }
10906         return;
10907     }
10908     DisplayMessage("", "");
10909     InitChessProgram(&second, FALSE);
10910     SendToProgram("force\n", &second);
10911     if (startedFromSetupPosition) {
10912         SendBoard(&second, backwardMostMove);
10913     if (appData.debugMode) {
10914         fprintf(debugFP, "Two Machines\n");
10915     }
10916     }
10917     for (i = backwardMostMove; i < forwardMostMove; i++) {
10918         SendMoveToProgram(i, &second);
10919     }
10920
10921     gameMode = TwoMachinesPlay;
10922     pausing = FALSE;
10923     ModeHighlight();
10924     SetGameInfo();
10925     DisplayTwoMachinesTitle();
10926     firstMove = TRUE;
10927     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10928         onmove = &first;
10929     } else {
10930         onmove = &second;
10931     }
10932
10933     SendToProgram(first.computerString, &first);
10934     if (first.sendName) {
10935       sprintf(buf, "name %s\n", second.tidy);
10936       SendToProgram(buf, &first);
10937     }
10938     SendToProgram(second.computerString, &second);
10939     if (second.sendName) {
10940       sprintf(buf, "name %s\n", first.tidy);
10941       SendToProgram(buf, &second);
10942     }
10943
10944     ResetClocks();
10945     if (!first.sendTime || !second.sendTime) {
10946         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10947         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10948     }
10949     if (onmove->sendTime) {
10950       if (onmove->useColors) {
10951         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10952       }
10953       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10954     }
10955     if (onmove->useColors) {
10956       SendToProgram(onmove->twoMachinesColor, onmove);
10957     }
10958     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10959 //    SendToProgram("go\n", onmove);
10960     onmove->maybeThinking = TRUE;
10961     SetMachineThinkingEnables();
10962
10963     StartClocks();
10964
10965     if(bookHit) { // [HGM] book: simulate book reply
10966         static char bookMove[MSG_SIZ]; // a bit generous?
10967
10968         programStats.nodes = programStats.depth = programStats.time = 
10969         programStats.score = programStats.got_only_move = 0;
10970         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10971
10972         strcpy(bookMove, "move ");
10973         strcat(bookMove, bookHit);
10974         savedMessage = bookMove; // args for deferred call
10975         savedState = onmove;
10976         ScheduleDelayedEvent(DeferredBookMove, 1);
10977     }
10978 }
10979
10980 void
10981 TrainingEvent()
10982 {
10983     if (gameMode == Training) {
10984       SetTrainingModeOff();
10985       gameMode = PlayFromGameFile;
10986       DisplayMessage("", _("Training mode off"));
10987     } else {
10988       gameMode = Training;
10989       animateTraining = appData.animate;
10990
10991       /* make sure we are not already at the end of the game */
10992       if (currentMove < forwardMostMove) {
10993         SetTrainingModeOn();
10994         DisplayMessage("", _("Training mode on"));
10995       } else {
10996         gameMode = PlayFromGameFile;
10997         DisplayError(_("Already at end of game"), 0);
10998       }
10999     }
11000     ModeHighlight();
11001 }
11002
11003 void
11004 IcsClientEvent()
11005 {
11006     if (!appData.icsActive) return;
11007     switch (gameMode) {
11008       case IcsPlayingWhite:
11009       case IcsPlayingBlack:
11010       case IcsObserving:
11011       case IcsIdle:
11012       case BeginningOfGame:
11013       case IcsExamining:
11014         return;
11015
11016       case EditGame:
11017         break;
11018
11019       case EditPosition:
11020         EditPositionDone(TRUE);
11021         break;
11022
11023       case AnalyzeMode:
11024       case AnalyzeFile:
11025         ExitAnalyzeMode();
11026         break;
11027         
11028       default:
11029         EditGameEvent();
11030         break;
11031     }
11032
11033     gameMode = IcsIdle;
11034     ModeHighlight();
11035     return;
11036 }
11037
11038
11039 void
11040 EditGameEvent()
11041 {
11042     int i;
11043
11044     switch (gameMode) {
11045       case Training:
11046         SetTrainingModeOff();
11047         break;
11048       case MachinePlaysWhite:
11049       case MachinePlaysBlack:
11050       case BeginningOfGame:
11051         SendToProgram("force\n", &first);
11052         SetUserThinkingEnables();
11053         break;
11054       case PlayFromGameFile:
11055         (void) StopLoadGameTimer();
11056         if (gameFileFP != NULL) {
11057             gameFileFP = NULL;
11058         }
11059         break;
11060       case EditPosition:
11061         EditPositionDone(TRUE);
11062         break;
11063       case AnalyzeMode:
11064       case AnalyzeFile:
11065         ExitAnalyzeMode();
11066         SendToProgram("force\n", &first);
11067         break;
11068       case TwoMachinesPlay:
11069         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11070         ResurrectChessProgram();
11071         SetUserThinkingEnables();
11072         break;
11073       case EndOfGame:
11074         ResurrectChessProgram();
11075         break;
11076       case IcsPlayingBlack:
11077       case IcsPlayingWhite:
11078         DisplayError(_("Warning: You are still playing a game"), 0);
11079         break;
11080       case IcsObserving:
11081         DisplayError(_("Warning: You are still observing a game"), 0);
11082         break;
11083       case IcsExamining:
11084         DisplayError(_("Warning: You are still examining a game"), 0);
11085         break;
11086       case IcsIdle:
11087         break;
11088       case EditGame:
11089       default:
11090         return;
11091     }
11092     
11093     pausing = FALSE;
11094     StopClocks();
11095     first.offeredDraw = second.offeredDraw = 0;
11096
11097     if (gameMode == PlayFromGameFile) {
11098         whiteTimeRemaining = timeRemaining[0][currentMove];
11099         blackTimeRemaining = timeRemaining[1][currentMove];
11100         DisplayTitle("");
11101     }
11102
11103     if (gameMode == MachinePlaysWhite ||
11104         gameMode == MachinePlaysBlack ||
11105         gameMode == TwoMachinesPlay ||
11106         gameMode == EndOfGame) {
11107         i = forwardMostMove;
11108         while (i > currentMove) {
11109             SendToProgram("undo\n", &first);
11110             i--;
11111         }
11112         whiteTimeRemaining = timeRemaining[0][currentMove];
11113         blackTimeRemaining = timeRemaining[1][currentMove];
11114         DisplayBothClocks();
11115         if (whiteFlag || blackFlag) {
11116             whiteFlag = blackFlag = 0;
11117         }
11118         DisplayTitle("");
11119     }           
11120     
11121     gameMode = EditGame;
11122     ModeHighlight();
11123     SetGameInfo();
11124 }
11125
11126
11127 void
11128 EditPositionEvent()
11129 {
11130     if (gameMode == EditPosition) {
11131         EditGameEvent();
11132         return;
11133     }
11134     
11135     EditGameEvent();
11136     if (gameMode != EditGame) return;
11137     
11138     gameMode = EditPosition;
11139     ModeHighlight();
11140     SetGameInfo();
11141     if (currentMove > 0)
11142       CopyBoard(boards[0], boards[currentMove]);
11143     
11144     blackPlaysFirst = !WhiteOnMove(currentMove);
11145     ResetClocks();
11146     currentMove = forwardMostMove = backwardMostMove = 0;
11147     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11148     DisplayMove(-1);
11149 }
11150
11151 void
11152 ExitAnalyzeMode()
11153 {
11154     /* [DM] icsEngineAnalyze - possible call from other functions */
11155     if (appData.icsEngineAnalyze) {
11156         appData.icsEngineAnalyze = FALSE;
11157
11158         DisplayMessage("",_("Close ICS engine analyze..."));
11159     }
11160     if (first.analysisSupport && first.analyzing) {
11161       SendToProgram("exit\n", &first);
11162       first.analyzing = FALSE;
11163     }
11164     thinkOutput[0] = NULLCHAR;
11165 }
11166
11167 void
11168 EditPositionDone(Boolean fakeRights)
11169 {
11170     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11171
11172     startedFromSetupPosition = TRUE;
11173     InitChessProgram(&first, FALSE);
11174     if(fakeRights)  
11175       { /* don't do this if we just pasted FEN */
11176         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11177         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11178           {
11179             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11180             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11181           } 
11182         else 
11183           castlingRights[0][2] = -1;
11184         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11185           {
11186             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11187             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11188           } 
11189         else 
11190           castlingRights[0][5] = -1;
11191       }
11192     SendToProgram("force\n", &first);
11193     if (blackPlaysFirst) {
11194         strcpy(moveList[0], "");
11195         strcpy(parseList[0], "");
11196         currentMove = forwardMostMove = backwardMostMove = 1;
11197         CopyBoard(boards[1], boards[0]);
11198         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11199         { int i;
11200           epStatus[1] = epStatus[0];
11201           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11202         }
11203     } else {
11204         currentMove = forwardMostMove = backwardMostMove = 0;
11205     }
11206     SendBoard(&first, forwardMostMove);
11207     if (appData.debugMode) {
11208         fprintf(debugFP, "EditPosDone\n");
11209     }
11210     DisplayTitle("");
11211     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11212     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11213     gameMode = EditGame;
11214     ModeHighlight();
11215     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11216     ClearHighlights(); /* [AS] */
11217 }
11218
11219 /* Pause for `ms' milliseconds */
11220 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11221 void
11222 TimeDelay(ms)
11223      long ms;
11224 {
11225     TimeMark m1, m2;
11226
11227     GetTimeMark(&m1);
11228     do {
11229         GetTimeMark(&m2);
11230     } while (SubtractTimeMarks(&m2, &m1) < ms);
11231 }
11232
11233 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11234 void
11235 SendMultiLineToICS(buf)
11236      char *buf;
11237 {
11238     char temp[MSG_SIZ+1], *p;
11239     int len;
11240
11241     len = strlen(buf);
11242     if (len > MSG_SIZ)
11243       len = MSG_SIZ;
11244   
11245     strncpy(temp, buf, len);
11246     temp[len] = 0;
11247
11248     p = temp;
11249     while (*p) {
11250         if (*p == '\n' || *p == '\r')
11251           *p = ' ';
11252         ++p;
11253     }
11254
11255     strcat(temp, "\n");
11256     SendToICS(temp);
11257     SendToPlayer(temp, strlen(temp));
11258 }
11259
11260 void
11261 SetWhiteToPlayEvent()
11262 {
11263     if (gameMode == EditPosition) {
11264         blackPlaysFirst = FALSE;
11265         DisplayBothClocks();    /* works because currentMove is 0 */
11266     } else if (gameMode == IcsExamining) {
11267         SendToICS(ics_prefix);
11268         SendToICS("tomove white\n");
11269     }
11270 }
11271
11272 void
11273 SetBlackToPlayEvent()
11274 {
11275     if (gameMode == EditPosition) {
11276         blackPlaysFirst = TRUE;
11277         currentMove = 1;        /* kludge */
11278         DisplayBothClocks();
11279         currentMove = 0;
11280     } else if (gameMode == IcsExamining) {
11281         SendToICS(ics_prefix);
11282         SendToICS("tomove black\n");
11283     }
11284 }
11285
11286 void
11287 EditPositionMenuEvent(selection, x, y)
11288      ChessSquare selection;
11289      int x, y;
11290 {
11291     char buf[MSG_SIZ];
11292     ChessSquare piece = boards[0][y][x];
11293
11294     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11295
11296     switch (selection) {
11297       case ClearBoard:
11298         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11299             SendToICS(ics_prefix);
11300             SendToICS("bsetup clear\n");
11301         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11302             SendToICS(ics_prefix);
11303             SendToICS("clearboard\n");
11304         } else {
11305             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11306                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11307                 for (y = 0; y < BOARD_HEIGHT; y++) {
11308                     if (gameMode == IcsExamining) {
11309                         if (boards[currentMove][y][x] != EmptySquare) {
11310                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11311                                     AAA + x, ONE + y);
11312                             SendToICS(buf);
11313                         }
11314                     } else {
11315                         boards[0][y][x] = p;
11316                     }
11317                 }
11318             }
11319         }
11320         if (gameMode == EditPosition) {
11321             DrawPosition(FALSE, boards[0]);
11322         }
11323         break;
11324
11325       case WhitePlay:
11326         SetWhiteToPlayEvent();
11327         break;
11328
11329       case BlackPlay:
11330         SetBlackToPlayEvent();
11331         break;
11332
11333       case EmptySquare:
11334         if (gameMode == IcsExamining) {
11335             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11336             SendToICS(buf);
11337         } else {
11338             boards[0][y][x] = EmptySquare;
11339             DrawPosition(FALSE, boards[0]);
11340         }
11341         break;
11342
11343       case PromotePiece:
11344         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11345            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11346             selection = (ChessSquare) (PROMOTED piece);
11347         } else if(piece == EmptySquare) selection = WhiteSilver;
11348         else selection = (ChessSquare)((int)piece - 1);
11349         goto defaultlabel;
11350
11351       case DemotePiece:
11352         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11353            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11354             selection = (ChessSquare) (DEMOTED piece);
11355         } else if(piece == EmptySquare) selection = BlackSilver;
11356         else selection = (ChessSquare)((int)piece + 1);       
11357         goto defaultlabel;
11358
11359       case WhiteQueen:
11360       case BlackQueen:
11361         if(gameInfo.variant == VariantShatranj ||
11362            gameInfo.variant == VariantXiangqi  ||
11363            gameInfo.variant == VariantCourier    )
11364             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11365         goto defaultlabel;
11366
11367       case WhiteKing:
11368       case BlackKing:
11369         if(gameInfo.variant == VariantXiangqi)
11370             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11371         if(gameInfo.variant == VariantKnightmate)
11372             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11373       default:
11374         defaultlabel:
11375         if (gameMode == IcsExamining) {
11376             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11377                     PieceToChar(selection), AAA + x, ONE + y);
11378             SendToICS(buf);
11379         } else {
11380             boards[0][y][x] = selection;
11381             DrawPosition(FALSE, boards[0]);
11382         }
11383         break;
11384     }
11385 }
11386
11387
11388 void
11389 DropMenuEvent(selection, x, y)
11390      ChessSquare selection;
11391      int x, y;
11392 {
11393     ChessMove moveType;
11394
11395     switch (gameMode) {
11396       case IcsPlayingWhite:
11397       case MachinePlaysBlack:
11398         if (!WhiteOnMove(currentMove)) {
11399             DisplayMoveError(_("It is Black's turn"));
11400             return;
11401         }
11402         moveType = WhiteDrop;
11403         break;
11404       case IcsPlayingBlack:
11405       case MachinePlaysWhite:
11406         if (WhiteOnMove(currentMove)) {
11407             DisplayMoveError(_("It is White's turn"));
11408             return;
11409         }
11410         moveType = BlackDrop;
11411         break;
11412       case EditGame:
11413         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11414         break;
11415       default:
11416         return;
11417     }
11418
11419     if (moveType == BlackDrop && selection < BlackPawn) {
11420       selection = (ChessSquare) ((int) selection
11421                                  + (int) BlackPawn - (int) WhitePawn);
11422     }
11423     if (boards[currentMove][y][x] != EmptySquare) {
11424         DisplayMoveError(_("That square is occupied"));
11425         return;
11426     }
11427
11428     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11429 }
11430
11431 void
11432 AcceptEvent()
11433 {
11434     /* Accept a pending offer of any kind from opponent */
11435     
11436     if (appData.icsActive) {
11437         SendToICS(ics_prefix);
11438         SendToICS("accept\n");
11439     } else if (cmailMsgLoaded) {
11440         if (currentMove == cmailOldMove &&
11441             commentList[cmailOldMove] != NULL &&
11442             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11443                    "Black offers a draw" : "White offers a draw")) {
11444             TruncateGame();
11445             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11446             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11447         } else {
11448             DisplayError(_("There is no pending offer on this move"), 0);
11449             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11450         }
11451     } else {
11452         /* Not used for offers from chess program */
11453     }
11454 }
11455
11456 void
11457 DeclineEvent()
11458 {
11459     /* Decline a pending offer of any kind from opponent */
11460     
11461     if (appData.icsActive) {
11462         SendToICS(ics_prefix);
11463         SendToICS("decline\n");
11464     } else if (cmailMsgLoaded) {
11465         if (currentMove == cmailOldMove &&
11466             commentList[cmailOldMove] != NULL &&
11467             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11468                    "Black offers a draw" : "White offers a draw")) {
11469 #ifdef NOTDEF
11470             AppendComment(cmailOldMove, "Draw declined");
11471             DisplayComment(cmailOldMove - 1, "Draw declined");
11472 #endif /*NOTDEF*/
11473         } else {
11474             DisplayError(_("There is no pending offer on this move"), 0);
11475         }
11476     } else {
11477         /* Not used for offers from chess program */
11478     }
11479 }
11480
11481 void
11482 RematchEvent()
11483 {
11484     /* Issue ICS rematch command */
11485     if (appData.icsActive) {
11486         SendToICS(ics_prefix);
11487         SendToICS("rematch\n");
11488     }
11489 }
11490
11491 void
11492 CallFlagEvent()
11493 {
11494     /* Call your opponent's flag (claim a win on time) */
11495     if (appData.icsActive) {
11496         SendToICS(ics_prefix);
11497         SendToICS("flag\n");
11498     } else {
11499         switch (gameMode) {
11500           default:
11501             return;
11502           case MachinePlaysWhite:
11503             if (whiteFlag) {
11504                 if (blackFlag)
11505                   GameEnds(GameIsDrawn, "Both players ran out of time",
11506                            GE_PLAYER);
11507                 else
11508                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11509             } else {
11510                 DisplayError(_("Your opponent is not out of time"), 0);
11511             }
11512             break;
11513           case MachinePlaysBlack:
11514             if (blackFlag) {
11515                 if (whiteFlag)
11516                   GameEnds(GameIsDrawn, "Both players ran out of time",
11517                            GE_PLAYER);
11518                 else
11519                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11520             } else {
11521                 DisplayError(_("Your opponent is not out of time"), 0);
11522             }
11523             break;
11524         }
11525     }
11526 }
11527
11528 void
11529 DrawEvent()
11530 {
11531     /* Offer draw or accept pending draw offer from opponent */
11532     
11533     if (appData.icsActive) {
11534         /* Note: tournament rules require draw offers to be
11535            made after you make your move but before you punch
11536            your clock.  Currently ICS doesn't let you do that;
11537            instead, you immediately punch your clock after making
11538            a move, but you can offer a draw at any time. */
11539         
11540         SendToICS(ics_prefix);
11541         SendToICS("draw\n");
11542     } else if (cmailMsgLoaded) {
11543         if (currentMove == cmailOldMove &&
11544             commentList[cmailOldMove] != NULL &&
11545             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11546                    "Black offers a draw" : "White offers a draw")) {
11547             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11548             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11549         } else if (currentMove == cmailOldMove + 1) {
11550             char *offer = WhiteOnMove(cmailOldMove) ?
11551               "White offers a draw" : "Black offers a draw";
11552             AppendComment(currentMove, offer);
11553             DisplayComment(currentMove - 1, offer);
11554             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11555         } else {
11556             DisplayError(_("You must make your move before offering a draw"), 0);
11557             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11558         }
11559     } else if (first.offeredDraw) {
11560         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11561     } else {
11562         if (first.sendDrawOffers) {
11563             SendToProgram("draw\n", &first);
11564             userOfferedDraw = TRUE;
11565         }
11566     }
11567 }
11568
11569 void
11570 AdjournEvent()
11571 {
11572     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11573     
11574     if (appData.icsActive) {
11575         SendToICS(ics_prefix);
11576         SendToICS("adjourn\n");
11577     } else {
11578         /* Currently GNU Chess doesn't offer or accept Adjourns */
11579     }
11580 }
11581
11582
11583 void
11584 AbortEvent()
11585 {
11586     /* Offer Abort or accept pending Abort offer from opponent */
11587     
11588     if (appData.icsActive) {
11589         SendToICS(ics_prefix);
11590         SendToICS("abort\n");
11591     } else {
11592         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11593     }
11594 }
11595
11596 void
11597 ResignEvent()
11598 {
11599     /* Resign.  You can do this even if it's not your turn. */
11600     
11601     if (appData.icsActive) {
11602         SendToICS(ics_prefix);
11603         SendToICS("resign\n");
11604     } else {
11605         switch (gameMode) {
11606           case MachinePlaysWhite:
11607             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11608             break;
11609           case MachinePlaysBlack:
11610             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11611             break;
11612           case EditGame:
11613             if (cmailMsgLoaded) {
11614                 TruncateGame();
11615                 if (WhiteOnMove(cmailOldMove)) {
11616                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11617                 } else {
11618                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11619                 }
11620                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11621             }
11622             break;
11623           default:
11624             break;
11625         }
11626     }
11627 }
11628
11629
11630 void
11631 StopObservingEvent()
11632 {
11633     /* Stop observing current games */
11634     SendToICS(ics_prefix);
11635     SendToICS("unobserve\n");
11636 }
11637
11638 void
11639 StopExaminingEvent()
11640 {
11641     /* Stop observing current game */
11642     SendToICS(ics_prefix);
11643     SendToICS("unexamine\n");
11644 }
11645
11646 void
11647 ForwardInner(target)
11648      int target;
11649 {
11650     int limit;
11651
11652     if (appData.debugMode)
11653         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11654                 target, currentMove, forwardMostMove);
11655
11656     if (gameMode == EditPosition)
11657       return;
11658
11659     if (gameMode == PlayFromGameFile && !pausing)
11660       PauseEvent();
11661     
11662     if (gameMode == IcsExamining && pausing)
11663       limit = pauseExamForwardMostMove;
11664     else
11665       limit = forwardMostMove;
11666     
11667     if (target > limit) target = limit;
11668
11669     if (target > 0 && moveList[target - 1][0]) {
11670         int fromX, fromY, toX, toY;
11671         toX = moveList[target - 1][2] - AAA;
11672         toY = moveList[target - 1][3] - ONE;
11673         if (moveList[target - 1][1] == '@') {
11674             if (appData.highlightLastMove) {
11675                 SetHighlights(-1, -1, toX, toY);
11676             }
11677         } else {
11678             fromX = moveList[target - 1][0] - AAA;
11679             fromY = moveList[target - 1][1] - ONE;
11680             if (target == currentMove + 1) {
11681                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11682             }
11683             if (appData.highlightLastMove) {
11684                 SetHighlights(fromX, fromY, toX, toY);
11685             }
11686         }
11687     }
11688     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11689         gameMode == Training || gameMode == PlayFromGameFile || 
11690         gameMode == AnalyzeFile) {
11691         while (currentMove < target) {
11692             SendMoveToProgram(currentMove++, &first);
11693         }
11694     } else {
11695         currentMove = target;
11696     }
11697     
11698     if (gameMode == EditGame || gameMode == EndOfGame) {
11699         whiteTimeRemaining = timeRemaining[0][currentMove];
11700         blackTimeRemaining = timeRemaining[1][currentMove];
11701     }
11702     DisplayBothClocks();
11703     DisplayMove(currentMove - 1);
11704     DrawPosition(FALSE, boards[currentMove]);
11705     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11706     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11707         DisplayComment(currentMove - 1, commentList[currentMove]);
11708     }
11709 }
11710
11711
11712 void
11713 ForwardEvent()
11714 {
11715     if (gameMode == IcsExamining && !pausing) {
11716         SendToICS(ics_prefix);
11717         SendToICS("forward\n");
11718     } else {
11719         ForwardInner(currentMove + 1);
11720     }
11721 }
11722
11723 void
11724 ToEndEvent()
11725 {
11726     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11727         /* to optimze, we temporarily turn off analysis mode while we feed
11728          * the remaining moves to the engine. Otherwise we get analysis output
11729          * after each move.
11730          */ 
11731         if (first.analysisSupport) {
11732           SendToProgram("exit\nforce\n", &first);
11733           first.analyzing = FALSE;
11734         }
11735     }
11736         
11737     if (gameMode == IcsExamining && !pausing) {
11738         SendToICS(ics_prefix);
11739         SendToICS("forward 999999\n");
11740     } else {
11741         ForwardInner(forwardMostMove);
11742     }
11743
11744     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11745         /* we have fed all the moves, so reactivate analysis mode */
11746         SendToProgram("analyze\n", &first);
11747         first.analyzing = TRUE;
11748         /*first.maybeThinking = TRUE;*/
11749         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11750     }
11751 }
11752
11753 void
11754 BackwardInner(target)
11755      int target;
11756 {
11757     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11758
11759     if (appData.debugMode)
11760         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11761                 target, currentMove, forwardMostMove);
11762
11763     if (gameMode == EditPosition) return;
11764     if (currentMove <= backwardMostMove) {
11765         ClearHighlights();
11766         DrawPosition(full_redraw, boards[currentMove]);
11767         return;
11768     }
11769     if (gameMode == PlayFromGameFile && !pausing)
11770       PauseEvent();
11771     
11772     if (moveList[target][0]) {
11773         int fromX, fromY, toX, toY;
11774         toX = moveList[target][2] - AAA;
11775         toY = moveList[target][3] - ONE;
11776         if (moveList[target][1] == '@') {
11777             if (appData.highlightLastMove) {
11778                 SetHighlights(-1, -1, toX, toY);
11779             }
11780         } else {
11781             fromX = moveList[target][0] - AAA;
11782             fromY = moveList[target][1] - ONE;
11783             if (target == currentMove - 1) {
11784                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11785             }
11786             if (appData.highlightLastMove) {
11787                 SetHighlights(fromX, fromY, toX, toY);
11788             }
11789         }
11790     }
11791     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11792         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11793         while (currentMove > target) {
11794             SendToProgram("undo\n", &first);
11795             currentMove--;
11796         }
11797     } else {
11798         currentMove = target;
11799     }
11800     
11801     if (gameMode == EditGame || gameMode == EndOfGame) {
11802         whiteTimeRemaining = timeRemaining[0][currentMove];
11803         blackTimeRemaining = timeRemaining[1][currentMove];
11804     }
11805     DisplayBothClocks();
11806     DisplayMove(currentMove - 1);
11807     DrawPosition(full_redraw, boards[currentMove]);
11808     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11809     // [HGM] PV info: routine tests if comment empty
11810     DisplayComment(currentMove - 1, commentList[currentMove]);
11811 }
11812
11813 void
11814 BackwardEvent()
11815 {
11816     if (gameMode == IcsExamining && !pausing) {
11817         SendToICS(ics_prefix);
11818         SendToICS("backward\n");
11819     } else {
11820         BackwardInner(currentMove - 1);
11821     }
11822 }
11823
11824 void
11825 ToStartEvent()
11826 {
11827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11828         /* to optimize, we temporarily turn off analysis mode while we undo
11829          * all the moves. Otherwise we get analysis output after each undo.
11830          */ 
11831         if (first.analysisSupport) {
11832           SendToProgram("exit\nforce\n", &first);
11833           first.analyzing = FALSE;
11834         }
11835     }
11836
11837     if (gameMode == IcsExamining && !pausing) {
11838         SendToICS(ics_prefix);
11839         SendToICS("backward 999999\n");
11840     } else {
11841         BackwardInner(backwardMostMove);
11842     }
11843
11844     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11845         /* we have fed all the moves, so reactivate analysis mode */
11846         SendToProgram("analyze\n", &first);
11847         first.analyzing = TRUE;
11848         /*first.maybeThinking = TRUE;*/
11849         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11850     }
11851 }
11852
11853 void
11854 ToNrEvent(int to)
11855 {
11856   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11857   if (to >= forwardMostMove) to = forwardMostMove;
11858   if (to <= backwardMostMove) to = backwardMostMove;
11859   if (to < currentMove) {
11860     BackwardInner(to);
11861   } else {
11862     ForwardInner(to);
11863   }
11864 }
11865
11866 void
11867 RevertEvent()
11868 {
11869     if (gameMode != IcsExamining) {
11870         DisplayError(_("You are not examining a game"), 0);
11871         return;
11872     }
11873     if (pausing) {
11874         DisplayError(_("You can't revert while pausing"), 0);
11875         return;
11876     }
11877     SendToICS(ics_prefix);
11878     SendToICS("revert\n");
11879 }
11880
11881 void
11882 RetractMoveEvent()
11883 {
11884     switch (gameMode) {
11885       case MachinePlaysWhite:
11886       case MachinePlaysBlack:
11887         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11888             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11889             return;
11890         }
11891         if (forwardMostMove < 2) return;
11892         currentMove = forwardMostMove = forwardMostMove - 2;
11893         whiteTimeRemaining = timeRemaining[0][currentMove];
11894         blackTimeRemaining = timeRemaining[1][currentMove];
11895         DisplayBothClocks();
11896         DisplayMove(currentMove - 1);
11897         ClearHighlights();/*!! could figure this out*/
11898         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11899         SendToProgram("remove\n", &first);
11900         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11901         break;
11902
11903       case BeginningOfGame:
11904       default:
11905         break;
11906
11907       case IcsPlayingWhite:
11908       case IcsPlayingBlack:
11909         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11910             SendToICS(ics_prefix);
11911             SendToICS("takeback 2\n");
11912         } else {
11913             SendToICS(ics_prefix);
11914             SendToICS("takeback 1\n");
11915         }
11916         break;
11917     }
11918 }
11919
11920 void
11921 MoveNowEvent()
11922 {
11923     ChessProgramState *cps;
11924
11925     switch (gameMode) {
11926       case MachinePlaysWhite:
11927         if (!WhiteOnMove(forwardMostMove)) {
11928             DisplayError(_("It is your turn"), 0);
11929             return;
11930         }
11931         cps = &first;
11932         break;
11933       case MachinePlaysBlack:
11934         if (WhiteOnMove(forwardMostMove)) {
11935             DisplayError(_("It is your turn"), 0);
11936             return;
11937         }
11938         cps = &first;
11939         break;
11940       case TwoMachinesPlay:
11941         if (WhiteOnMove(forwardMostMove) ==
11942             (first.twoMachinesColor[0] == 'w')) {
11943             cps = &first;
11944         } else {
11945             cps = &second;
11946         }
11947         break;
11948       case BeginningOfGame:
11949       default:
11950         return;
11951     }
11952     SendToProgram("?\n", cps);
11953 }
11954
11955 void
11956 TruncateGameEvent()
11957 {
11958     EditGameEvent();
11959     if (gameMode != EditGame) return;
11960     TruncateGame();
11961 }
11962
11963 void
11964 TruncateGame()
11965 {
11966     if (forwardMostMove > currentMove) {
11967         if (gameInfo.resultDetails != NULL) {
11968             free(gameInfo.resultDetails);
11969             gameInfo.resultDetails = NULL;
11970             gameInfo.result = GameUnfinished;
11971         }
11972         forwardMostMove = currentMove;
11973         HistorySet(parseList, backwardMostMove, forwardMostMove,
11974                    currentMove-1);
11975     }
11976 }
11977
11978 void
11979 HintEvent()
11980 {
11981     if (appData.noChessProgram) return;
11982     switch (gameMode) {
11983       case MachinePlaysWhite:
11984         if (WhiteOnMove(forwardMostMove)) {
11985             DisplayError(_("Wait until your turn"), 0);
11986             return;
11987         }
11988         break;
11989       case BeginningOfGame:
11990       case MachinePlaysBlack:
11991         if (!WhiteOnMove(forwardMostMove)) {
11992             DisplayError(_("Wait until your turn"), 0);
11993             return;
11994         }
11995         break;
11996       default:
11997         DisplayError(_("No hint available"), 0);
11998         return;
11999     }
12000     SendToProgram("hint\n", &first);
12001     hintRequested = TRUE;
12002 }
12003
12004 void
12005 BookEvent()
12006 {
12007     if (appData.noChessProgram) return;
12008     switch (gameMode) {
12009       case MachinePlaysWhite:
12010         if (WhiteOnMove(forwardMostMove)) {
12011             DisplayError(_("Wait until your turn"), 0);
12012             return;
12013         }
12014         break;
12015       case BeginningOfGame:
12016       case MachinePlaysBlack:
12017         if (!WhiteOnMove(forwardMostMove)) {
12018             DisplayError(_("Wait until your turn"), 0);
12019             return;
12020         }
12021         break;
12022       case EditPosition:
12023         EditPositionDone(TRUE);
12024         break;
12025       case TwoMachinesPlay:
12026         return;
12027       default:
12028         break;
12029     }
12030     SendToProgram("bk\n", &first);
12031     bookOutput[0] = NULLCHAR;
12032     bookRequested = TRUE;
12033 }
12034
12035 void
12036 AboutGameEvent()
12037 {
12038     char *tags = PGNTags(&gameInfo);
12039     TagsPopUp(tags, CmailMsg());
12040     free(tags);
12041 }
12042
12043 /* end button procedures */
12044
12045 void
12046 PrintPosition(fp, move)
12047      FILE *fp;
12048      int move;
12049 {
12050     int i, j;
12051     
12052     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12053         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12054             char c = PieceToChar(boards[move][i][j]);
12055             fputc(c == 'x' ? '.' : c, fp);
12056             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12057         }
12058     }
12059     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12060       fprintf(fp, "white to play\n");
12061     else
12062       fprintf(fp, "black to play\n");
12063 }
12064
12065 void
12066 PrintOpponents(fp)
12067      FILE *fp;
12068 {
12069     if (gameInfo.white != NULL) {
12070         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12071     } else {
12072         fprintf(fp, "\n");
12073     }
12074 }
12075
12076 /* Find last component of program's own name, using some heuristics */
12077 void
12078 TidyProgramName(prog, host, buf)
12079      char *prog, *host, buf[MSG_SIZ];
12080 {
12081     char *p, *q;
12082     int local = (strcmp(host, "localhost") == 0);
12083     while (!local && (p = strchr(prog, ';')) != NULL) {
12084         p++;
12085         while (*p == ' ') p++;
12086         prog = p;
12087     }
12088     if (*prog == '"' || *prog == '\'') {
12089         q = strchr(prog + 1, *prog);
12090     } else {
12091         q = strchr(prog, ' ');
12092     }
12093     if (q == NULL) q = prog + strlen(prog);
12094     p = q;
12095     while (p >= prog && *p != '/' && *p != '\\') p--;
12096     p++;
12097     if(p == prog && *p == '"') p++;
12098     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12099     memcpy(buf, p, q - p);
12100     buf[q - p] = NULLCHAR;
12101     if (!local) {
12102         strcat(buf, "@");
12103         strcat(buf, host);
12104     }
12105 }
12106
12107 char *
12108 TimeControlTagValue()
12109 {
12110     char buf[MSG_SIZ];
12111     if (!appData.clockMode) {
12112         strcpy(buf, "-");
12113     } else if (movesPerSession > 0) {
12114         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12115     } else if (timeIncrement == 0) {
12116         sprintf(buf, "%ld", timeControl/1000);
12117     } else {
12118         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12119     }
12120     return StrSave(buf);
12121 }
12122
12123 void
12124 SetGameInfo()
12125 {
12126     /* This routine is used only for certain modes */
12127     VariantClass v = gameInfo.variant;
12128     ClearGameInfo(&gameInfo);
12129     gameInfo.variant = v;
12130
12131     switch (gameMode) {
12132       case MachinePlaysWhite:
12133         gameInfo.event = StrSave( appData.pgnEventHeader );
12134         gameInfo.site = StrSave(HostName());
12135         gameInfo.date = PGNDate();
12136         gameInfo.round = StrSave("-");
12137         gameInfo.white = StrSave(first.tidy);
12138         gameInfo.black = StrSave(UserName());
12139         gameInfo.timeControl = TimeControlTagValue();
12140         break;
12141
12142       case MachinePlaysBlack:
12143         gameInfo.event = StrSave( appData.pgnEventHeader );
12144         gameInfo.site = StrSave(HostName());
12145         gameInfo.date = PGNDate();
12146         gameInfo.round = StrSave("-");
12147         gameInfo.white = StrSave(UserName());
12148         gameInfo.black = StrSave(first.tidy);
12149         gameInfo.timeControl = TimeControlTagValue();
12150         break;
12151
12152       case TwoMachinesPlay:
12153         gameInfo.event = StrSave( appData.pgnEventHeader );
12154         gameInfo.site = StrSave(HostName());
12155         gameInfo.date = PGNDate();
12156         if (matchGame > 0) {
12157             char buf[MSG_SIZ];
12158             sprintf(buf, "%d", matchGame);
12159             gameInfo.round = StrSave(buf);
12160         } else {
12161             gameInfo.round = StrSave("-");
12162         }
12163         if (first.twoMachinesColor[0] == 'w') {
12164             gameInfo.white = StrSave(first.tidy);
12165             gameInfo.black = StrSave(second.tidy);
12166         } else {
12167             gameInfo.white = StrSave(second.tidy);
12168             gameInfo.black = StrSave(first.tidy);
12169         }
12170         gameInfo.timeControl = TimeControlTagValue();
12171         break;
12172
12173       case EditGame:
12174         gameInfo.event = StrSave("Edited game");
12175         gameInfo.site = StrSave(HostName());
12176         gameInfo.date = PGNDate();
12177         gameInfo.round = StrSave("-");
12178         gameInfo.white = StrSave("-");
12179         gameInfo.black = StrSave("-");
12180         break;
12181
12182       case EditPosition:
12183         gameInfo.event = StrSave("Edited position");
12184         gameInfo.site = StrSave(HostName());
12185         gameInfo.date = PGNDate();
12186         gameInfo.round = StrSave("-");
12187         gameInfo.white = StrSave("-");
12188         gameInfo.black = StrSave("-");
12189         break;
12190
12191       case IcsPlayingWhite:
12192       case IcsPlayingBlack:
12193       case IcsObserving:
12194       case IcsExamining:
12195         break;
12196
12197       case PlayFromGameFile:
12198         gameInfo.event = StrSave("Game from non-PGN file");
12199         gameInfo.site = StrSave(HostName());
12200         gameInfo.date = PGNDate();
12201         gameInfo.round = StrSave("-");
12202         gameInfo.white = StrSave("?");
12203         gameInfo.black = StrSave("?");
12204         break;
12205
12206       default:
12207         break;
12208     }
12209 }
12210
12211 void
12212 ReplaceComment(index, text)
12213      int index;
12214      char *text;
12215 {
12216     int len;
12217
12218     while (*text == '\n') text++;
12219     len = strlen(text);
12220     while (len > 0 && text[len - 1] == '\n') len--;
12221
12222     if (commentList[index] != NULL)
12223       free(commentList[index]);
12224
12225     if (len == 0) {
12226         commentList[index] = NULL;
12227         return;
12228     }
12229     commentList[index] = (char *) malloc(len + 2);
12230     strncpy(commentList[index], text, len);
12231     commentList[index][len] = '\n';
12232     commentList[index][len + 1] = NULLCHAR;
12233 }
12234
12235 void
12236 CrushCRs(text)
12237      char *text;
12238 {
12239   char *p = text;
12240   char *q = text;
12241   char ch;
12242
12243   do {
12244     ch = *p++;
12245     if (ch == '\r') continue;
12246     *q++ = ch;
12247   } while (ch != '\0');
12248 }
12249
12250 void
12251 AppendComment(index, text)
12252      int index;
12253      char *text;
12254 {
12255     int oldlen, len;
12256     char *old;
12257
12258     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12259
12260     CrushCRs(text);
12261     while (*text == '\n') text++;
12262     len = strlen(text);
12263     while (len > 0 && text[len - 1] == '\n') len--;
12264
12265     if (len == 0) return;
12266
12267     if (commentList[index] != NULL) {
12268         old = commentList[index];
12269         oldlen = strlen(old);
12270         commentList[index] = (char *) malloc(oldlen + len + 2);
12271         strcpy(commentList[index], old);
12272         free(old);
12273         strncpy(&commentList[index][oldlen], text, len);
12274         commentList[index][oldlen + len] = '\n';
12275         commentList[index][oldlen + len + 1] = NULLCHAR;
12276     } else {
12277         commentList[index] = (char *) malloc(len + 2);
12278         strncpy(commentList[index], text, len);
12279         commentList[index][len] = '\n';
12280         commentList[index][len + 1] = NULLCHAR;
12281     }
12282 }
12283
12284 static char * FindStr( char * text, char * sub_text )
12285 {
12286     char * result = strstr( text, sub_text );
12287
12288     if( result != NULL ) {
12289         result += strlen( sub_text );
12290     }
12291
12292     return result;
12293 }
12294
12295 /* [AS] Try to extract PV info from PGN comment */
12296 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12297 char *GetInfoFromComment( int index, char * text )
12298 {
12299     char * sep = text;
12300
12301     if( text != NULL && index > 0 ) {
12302         int score = 0;
12303         int depth = 0;
12304         int time = -1, sec = 0, deci;
12305         char * s_eval = FindStr( text, "[%eval " );
12306         char * s_emt = FindStr( text, "[%emt " );
12307
12308         if( s_eval != NULL || s_emt != NULL ) {
12309             /* New style */
12310             char delim;
12311
12312             if( s_eval != NULL ) {
12313                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12314                     return text;
12315                 }
12316
12317                 if( delim != ']' ) {
12318                     return text;
12319                 }
12320             }
12321
12322             if( s_emt != NULL ) {
12323             }
12324         }
12325         else {
12326             /* We expect something like: [+|-]nnn.nn/dd */
12327             int score_lo = 0;
12328
12329             sep = strchr( text, '/' );
12330             if( sep == NULL || sep < (text+4) ) {
12331                 return text;
12332             }
12333
12334             time = -1; sec = -1; deci = -1;
12335             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12336                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12337                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12338                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12339                 return text;
12340             }
12341
12342             if( score_lo < 0 || score_lo >= 100 ) {
12343                 return text;
12344             }
12345
12346             if(sec >= 0) time = 600*time + 10*sec; else
12347             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12348
12349             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12350
12351             /* [HGM] PV time: now locate end of PV info */
12352             while( *++sep >= '0' && *sep <= '9'); // strip depth
12353             if(time >= 0)
12354             while( *++sep >= '0' && *sep <= '9'); // strip time
12355             if(sec >= 0)
12356             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12357             if(deci >= 0)
12358             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12359             while(*sep == ' ') sep++;
12360         }
12361
12362         if( depth <= 0 ) {
12363             return text;
12364         }
12365
12366         if( time < 0 ) {
12367             time = -1;
12368         }
12369
12370         pvInfoList[index-1].depth = depth;
12371         pvInfoList[index-1].score = score;
12372         pvInfoList[index-1].time  = 10*time; // centi-sec
12373     }
12374     return sep;
12375 }
12376
12377 void
12378 SendToProgram(message, cps)
12379      char *message;
12380      ChessProgramState *cps;
12381 {
12382     int count, outCount, error;
12383     char buf[MSG_SIZ];
12384
12385     if (cps->pr == NULL) return;
12386     Attention(cps);
12387     
12388     if (appData.debugMode) {
12389         TimeMark now;
12390         GetTimeMark(&now);
12391         fprintf(debugFP, "%ld >%-6s: %s", 
12392                 SubtractTimeMarks(&now, &programStartTime),
12393                 cps->which, message);
12394     }
12395     
12396     count = strlen(message);
12397     outCount = OutputToProcess(cps->pr, message, count, &error);
12398     if (outCount < count && !exiting 
12399                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12400         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12401         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12402             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12403                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12404                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12405             } else {
12406                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12407             }
12408             gameInfo.resultDetails = StrSave(buf);
12409         }
12410         DisplayFatalError(buf, error, 1);
12411     }
12412 }
12413
12414 void
12415 ReceiveFromProgram(isr, closure, message, count, error)
12416      InputSourceRef isr;
12417      VOIDSTAR closure;
12418      char *message;
12419      int count;
12420      int error;
12421 {
12422     char *end_str;
12423     char buf[MSG_SIZ];
12424     ChessProgramState *cps = (ChessProgramState *)closure;
12425
12426     if (isr != cps->isr) return; /* Killed intentionally */
12427     if (count <= 0) {
12428         if (count == 0) {
12429             sprintf(buf,
12430                     _("Error: %s chess program (%s) exited unexpectedly"),
12431                     cps->which, cps->program);
12432         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12433                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12434                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12435                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12436                 } else {
12437                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12438                 }
12439                 gameInfo.resultDetails = StrSave(buf);
12440             }
12441             RemoveInputSource(cps->isr);
12442             DisplayFatalError(buf, 0, 1);
12443         } else {
12444             sprintf(buf,
12445                     _("Error reading from %s chess program (%s)"),
12446                     cps->which, cps->program);
12447             RemoveInputSource(cps->isr);
12448
12449             /* [AS] Program is misbehaving badly... kill it */
12450             if( count == -2 ) {
12451                 DestroyChildProcess( cps->pr, 9 );
12452                 cps->pr = NoProc;
12453             }
12454
12455             DisplayFatalError(buf, error, 1);
12456         }
12457         return;
12458     }
12459     
12460     if ((end_str = strchr(message, '\r')) != NULL)
12461       *end_str = NULLCHAR;
12462     if ((end_str = strchr(message, '\n')) != NULL)
12463       *end_str = NULLCHAR;
12464     
12465     if (appData.debugMode) {
12466         TimeMark now; int print = 1;
12467         char *quote = ""; char c; int i;
12468
12469         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12470                 char start = message[0];
12471                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12472                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12473                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12474                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12475                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12476                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12477                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12478                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12479                         { quote = "# "; print = (appData.engineComments == 2); }
12480                 message[0] = start; // restore original message
12481         }
12482         if(print) {
12483                 GetTimeMark(&now);
12484                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12485                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12486                         quote,
12487                         message);
12488         }
12489     }
12490
12491     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12492     if (appData.icsEngineAnalyze) {
12493         if (strstr(message, "whisper") != NULL ||
12494              strstr(message, "kibitz") != NULL || 
12495             strstr(message, "tellics") != NULL) return;
12496     }
12497
12498     HandleMachineMove(message, cps);
12499 }
12500
12501
12502 void
12503 SendTimeControl(cps, mps, tc, inc, sd, st)
12504      ChessProgramState *cps;
12505      int mps, inc, sd, st;
12506      long tc;
12507 {
12508     char buf[MSG_SIZ];
12509     int seconds;
12510
12511     if( timeControl_2 > 0 ) {
12512         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12513             tc = timeControl_2;
12514         }
12515     }
12516     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12517     inc /= cps->timeOdds;
12518     st  /= cps->timeOdds;
12519
12520     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12521
12522     if (st > 0) {
12523       /* Set exact time per move, normally using st command */
12524       if (cps->stKludge) {
12525         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12526         seconds = st % 60;
12527         if (seconds == 0) {
12528           sprintf(buf, "level 1 %d\n", st/60);
12529         } else {
12530           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12531         }
12532       } else {
12533         sprintf(buf, "st %d\n", st);
12534       }
12535     } else {
12536       /* Set conventional or incremental time control, using level command */
12537       if (seconds == 0) {
12538         /* Note old gnuchess bug -- minutes:seconds used to not work.
12539            Fixed in later versions, but still avoid :seconds
12540            when seconds is 0. */
12541         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12542       } else {
12543         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12544                 seconds, inc/1000);
12545       }
12546     }
12547     SendToProgram(buf, cps);
12548
12549     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12550     /* Orthogonally, limit search to given depth */
12551     if (sd > 0) {
12552       if (cps->sdKludge) {
12553         sprintf(buf, "depth\n%d\n", sd);
12554       } else {
12555         sprintf(buf, "sd %d\n", sd);
12556       }
12557       SendToProgram(buf, cps);
12558     }
12559
12560     if(cps->nps > 0) { /* [HGM] nps */
12561         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12562         else {
12563                 sprintf(buf, "nps %d\n", cps->nps);
12564               SendToProgram(buf, cps);
12565         }
12566     }
12567 }
12568
12569 ChessProgramState *WhitePlayer()
12570 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12571 {
12572     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12573        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12574         return &second;
12575     return &first;
12576 }
12577
12578 void
12579 SendTimeRemaining(cps, machineWhite)
12580      ChessProgramState *cps;
12581      int /*boolean*/ machineWhite;
12582 {
12583     char message[MSG_SIZ];
12584     long time, otime;
12585
12586     /* Note: this routine must be called when the clocks are stopped
12587        or when they have *just* been set or switched; otherwise
12588        it will be off by the time since the current tick started.
12589     */
12590     if (machineWhite) {
12591         time = whiteTimeRemaining / 10;
12592         otime = blackTimeRemaining / 10;
12593     } else {
12594         time = blackTimeRemaining / 10;
12595         otime = whiteTimeRemaining / 10;
12596     }
12597     /* [HGM] translate opponent's time by time-odds factor */
12598     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12599     if (appData.debugMode) {
12600         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12601     }
12602
12603     if (time <= 0) time = 1;
12604     if (otime <= 0) otime = 1;
12605     
12606     sprintf(message, "time %ld\n", time);
12607     SendToProgram(message, cps);
12608
12609     sprintf(message, "otim %ld\n", otime);
12610     SendToProgram(message, cps);
12611 }
12612
12613 int
12614 BoolFeature(p, name, loc, cps)
12615      char **p;
12616      char *name;
12617      int *loc;
12618      ChessProgramState *cps;
12619 {
12620   char buf[MSG_SIZ];
12621   int len = strlen(name);
12622   int val;
12623   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12624     (*p) += len + 1;
12625     sscanf(*p, "%d", &val);
12626     *loc = (val != 0);
12627     while (**p && **p != ' ') (*p)++;
12628     sprintf(buf, "accepted %s\n", name);
12629     SendToProgram(buf, cps);
12630     return TRUE;
12631   }
12632   return FALSE;
12633 }
12634
12635 int
12636 IntFeature(p, name, loc, cps)
12637      char **p;
12638      char *name;
12639      int *loc;
12640      ChessProgramState *cps;
12641 {
12642   char buf[MSG_SIZ];
12643   int len = strlen(name);
12644   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12645     (*p) += len + 1;
12646     sscanf(*p, "%d", loc);
12647     while (**p && **p != ' ') (*p)++;
12648     sprintf(buf, "accepted %s\n", name);
12649     SendToProgram(buf, cps);
12650     return TRUE;
12651   }
12652   return FALSE;
12653 }
12654
12655 int
12656 StringFeature(p, name, loc, cps)
12657      char **p;
12658      char *name;
12659      char loc[];
12660      ChessProgramState *cps;
12661 {
12662   char buf[MSG_SIZ];
12663   int len = strlen(name);
12664   if (strncmp((*p), name, len) == 0
12665       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12666     (*p) += len + 2;
12667     sscanf(*p, "%[^\"]", loc);
12668     while (**p && **p != '\"') (*p)++;
12669     if (**p == '\"') (*p)++;
12670     sprintf(buf, "accepted %s\n", name);
12671     SendToProgram(buf, cps);
12672     return TRUE;
12673   }
12674   return FALSE;
12675 }
12676
12677 int 
12678 ParseOption(Option *opt, ChessProgramState *cps)
12679 // [HGM] options: process the string that defines an engine option, and determine
12680 // name, type, default value, and allowed value range
12681 {
12682         char *p, *q, buf[MSG_SIZ];
12683         int n, min = (-1)<<31, max = 1<<31, def;
12684
12685         if(p = strstr(opt->name, " -spin ")) {
12686             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12687             if(max < min) max = min; // enforce consistency
12688             if(def < min) def = min;
12689             if(def > max) def = max;
12690             opt->value = def;
12691             opt->min = min;
12692             opt->max = max;
12693             opt->type = Spin;
12694         } else if((p = strstr(opt->name, " -slider "))) {
12695             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12696             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12697             if(max < min) max = min; // enforce consistency
12698             if(def < min) def = min;
12699             if(def > max) def = max;
12700             opt->value = def;
12701             opt->min = min;
12702             opt->max = max;
12703             opt->type = Spin; // Slider;
12704         } else if((p = strstr(opt->name, " -string "))) {
12705             opt->textValue = p+9;
12706             opt->type = TextBox;
12707         } else if((p = strstr(opt->name, " -file "))) {
12708             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12709             opt->textValue = p+7;
12710             opt->type = TextBox; // FileName;
12711         } else if((p = strstr(opt->name, " -path "))) {
12712             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12713             opt->textValue = p+7;
12714             opt->type = TextBox; // PathName;
12715         } else if(p = strstr(opt->name, " -check ")) {
12716             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12717             opt->value = (def != 0);
12718             opt->type = CheckBox;
12719         } else if(p = strstr(opt->name, " -combo ")) {
12720             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12721             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12722             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12723             opt->value = n = 0;
12724             while(q = StrStr(q, " /// ")) {
12725                 n++; *q = 0;    // count choices, and null-terminate each of them
12726                 q += 5;
12727                 if(*q == '*') { // remember default, which is marked with * prefix
12728                     q++;
12729                     opt->value = n;
12730                 }
12731                 cps->comboList[cps->comboCnt++] = q;
12732             }
12733             cps->comboList[cps->comboCnt++] = NULL;
12734             opt->max = n + 1;
12735             opt->type = ComboBox;
12736         } else if(p = strstr(opt->name, " -button")) {
12737             opt->type = Button;
12738         } else if(p = strstr(opt->name, " -save")) {
12739             opt->type = SaveButton;
12740         } else return FALSE;
12741         *p = 0; // terminate option name
12742         // now look if the command-line options define a setting for this engine option.
12743         if(cps->optionSettings && cps->optionSettings[0])
12744             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12745         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12746                 sprintf(buf, "option %s", p);
12747                 if(p = strstr(buf, ",")) *p = 0;
12748                 strcat(buf, "\n");
12749                 SendToProgram(buf, cps);
12750         }
12751         return TRUE;
12752 }
12753
12754 void
12755 FeatureDone(cps, val)
12756      ChessProgramState* cps;
12757      int val;
12758 {
12759   DelayedEventCallback cb = GetDelayedEvent();
12760   if ((cb == InitBackEnd3 && cps == &first) ||
12761       (cb == TwoMachinesEventIfReady && cps == &second)) {
12762     CancelDelayedEvent();
12763     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12764   }
12765   cps->initDone = val;
12766 }
12767
12768 /* Parse feature command from engine */
12769 void
12770 ParseFeatures(args, cps)
12771      char* args;
12772      ChessProgramState *cps;  
12773 {
12774   char *p = args;
12775   char *q;
12776   int val;
12777   char buf[MSG_SIZ];
12778
12779   for (;;) {
12780     while (*p == ' ') p++;
12781     if (*p == NULLCHAR) return;
12782
12783     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12784     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12785     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12786     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12787     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12788     if (BoolFeature(&p, "reuse", &val, cps)) {
12789       /* Engine can disable reuse, but can't enable it if user said no */
12790       if (!val) cps->reuse = FALSE;
12791       continue;
12792     }
12793     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12794     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12795       if (gameMode == TwoMachinesPlay) {
12796         DisplayTwoMachinesTitle();
12797       } else {
12798         DisplayTitle("");
12799       }
12800       continue;
12801     }
12802     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12803     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12804     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12805     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12806     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12807     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12808     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12809     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12810     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12811     if (IntFeature(&p, "done", &val, cps)) {
12812       FeatureDone(cps, val);
12813       continue;
12814     }
12815     /* Added by Tord: */
12816     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12817     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12818     /* End of additions by Tord */
12819
12820     /* [HGM] added features: */
12821     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12822     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12823     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12824     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12825     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12826     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12827     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12828         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12829             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12830             SendToProgram(buf, cps);
12831             continue;
12832         }
12833         if(cps->nrOptions >= MAX_OPTIONS) {
12834             cps->nrOptions--;
12835             sprintf(buf, "%s engine has too many options\n", cps->which);
12836             DisplayError(buf, 0);
12837         }
12838         continue;
12839     }
12840     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12841     /* End of additions by HGM */
12842
12843     /* unknown feature: complain and skip */
12844     q = p;
12845     while (*q && *q != '=') q++;
12846     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12847     SendToProgram(buf, cps);
12848     p = q;
12849     if (*p == '=') {
12850       p++;
12851       if (*p == '\"') {
12852         p++;
12853         while (*p && *p != '\"') p++;
12854         if (*p == '\"') p++;
12855       } else {
12856         while (*p && *p != ' ') p++;
12857       }
12858     }
12859   }
12860
12861 }
12862
12863 void
12864 PeriodicUpdatesEvent(newState)
12865      int newState;
12866 {
12867     if (newState == appData.periodicUpdates)
12868       return;
12869
12870     appData.periodicUpdates=newState;
12871
12872     /* Display type changes, so update it now */
12873 //    DisplayAnalysis();
12874
12875     /* Get the ball rolling again... */
12876     if (newState) {
12877         AnalysisPeriodicEvent(1);
12878         StartAnalysisClock();
12879     }
12880 }
12881
12882 void
12883 PonderNextMoveEvent(newState)
12884      int newState;
12885 {
12886     if (newState == appData.ponderNextMove) return;
12887     if (gameMode == EditPosition) EditPositionDone(TRUE);
12888     if (newState) {
12889         SendToProgram("hard\n", &first);
12890         if (gameMode == TwoMachinesPlay) {
12891             SendToProgram("hard\n", &second);
12892         }
12893     } else {
12894         SendToProgram("easy\n", &first);
12895         thinkOutput[0] = NULLCHAR;
12896         if (gameMode == TwoMachinesPlay) {
12897             SendToProgram("easy\n", &second);
12898         }
12899     }
12900     appData.ponderNextMove = newState;
12901 }
12902
12903 void
12904 NewSettingEvent(option, command, value)
12905      char *command;
12906      int option, value;
12907 {
12908     char buf[MSG_SIZ];
12909
12910     if (gameMode == EditPosition) EditPositionDone(TRUE);
12911     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12912     SendToProgram(buf, &first);
12913     if (gameMode == TwoMachinesPlay) {
12914         SendToProgram(buf, &second);
12915     }
12916 }
12917
12918 void
12919 ShowThinkingEvent()
12920 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12921 {
12922     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12923     int newState = appData.showThinking
12924         // [HGM] thinking: other features now need thinking output as well
12925         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12926     
12927     if (oldState == newState) return;
12928     oldState = newState;
12929     if (gameMode == EditPosition) EditPositionDone(TRUE);
12930     if (oldState) {
12931         SendToProgram("post\n", &first);
12932         if (gameMode == TwoMachinesPlay) {
12933             SendToProgram("post\n", &second);
12934         }
12935     } else {
12936         SendToProgram("nopost\n", &first);
12937         thinkOutput[0] = NULLCHAR;
12938         if (gameMode == TwoMachinesPlay) {
12939             SendToProgram("nopost\n", &second);
12940         }
12941     }
12942 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12943 }
12944
12945 void
12946 AskQuestionEvent(title, question, replyPrefix, which)
12947      char *title; char *question; char *replyPrefix; char *which;
12948 {
12949   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12950   if (pr == NoProc) return;
12951   AskQuestion(title, question, replyPrefix, pr);
12952 }
12953
12954 void
12955 DisplayMove(moveNumber)
12956      int moveNumber;
12957 {
12958     char message[MSG_SIZ];
12959     char res[MSG_SIZ];
12960     char cpThinkOutput[MSG_SIZ];
12961
12962     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12963     
12964     if (moveNumber == forwardMostMove - 1 || 
12965         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12966
12967         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12968
12969         if (strchr(cpThinkOutput, '\n')) {
12970             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12971         }
12972     } else {
12973         *cpThinkOutput = NULLCHAR;
12974     }
12975
12976     /* [AS] Hide thinking from human user */
12977     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12978         *cpThinkOutput = NULLCHAR;
12979         if( thinkOutput[0] != NULLCHAR ) {
12980             int i;
12981
12982             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12983                 cpThinkOutput[i] = '.';
12984             }
12985             cpThinkOutput[i] = NULLCHAR;
12986             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12987         }
12988     }
12989
12990     if (moveNumber == forwardMostMove - 1 &&
12991         gameInfo.resultDetails != NULL) {
12992         if (gameInfo.resultDetails[0] == NULLCHAR) {
12993             sprintf(res, " %s", PGNResult(gameInfo.result));
12994         } else {
12995             sprintf(res, " {%s} %s",
12996                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12997         }
12998     } else {
12999         res[0] = NULLCHAR;
13000     }
13001
13002     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13003         DisplayMessage(res, cpThinkOutput);
13004     } else {
13005         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13006                 WhiteOnMove(moveNumber) ? " " : ".. ",
13007                 parseList[moveNumber], res);
13008         DisplayMessage(message, cpThinkOutput);
13009     }
13010 }
13011
13012 void
13013 DisplayComment(moveNumber, text)
13014      int moveNumber;
13015      char *text;
13016 {
13017     char title[MSG_SIZ];
13018     char buf[8000]; // comment can be long!
13019     int score, depth;
13020     
13021     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13022       strcpy(title, "Comment");
13023     } else {
13024       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13025               WhiteOnMove(moveNumber) ? " " : ".. ",
13026               parseList[moveNumber]);
13027     }
13028     // [HGM] PV info: display PV info together with (or as) comment
13029     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13030       if(text == NULL) text = "";                                           
13031       score = pvInfoList[moveNumber].score;
13032       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13033               depth, (pvInfoList[moveNumber].time+50)/100, text);
13034       text = buf;
13035     }
13036     if (text != NULL && (appData.autoDisplayComment || commentUp))
13037         CommentPopUp(title, text);
13038 }
13039
13040 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13041  * might be busy thinking or pondering.  It can be omitted if your
13042  * gnuchess is configured to stop thinking immediately on any user
13043  * input.  However, that gnuchess feature depends on the FIONREAD
13044  * ioctl, which does not work properly on some flavors of Unix.
13045  */
13046 void
13047 Attention(cps)
13048      ChessProgramState *cps;
13049 {
13050 #if ATTENTION
13051     if (!cps->useSigint) return;
13052     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13053     switch (gameMode) {
13054       case MachinePlaysWhite:
13055       case MachinePlaysBlack:
13056       case TwoMachinesPlay:
13057       case IcsPlayingWhite:
13058       case IcsPlayingBlack:
13059       case AnalyzeMode:
13060       case AnalyzeFile:
13061         /* Skip if we know it isn't thinking */
13062         if (!cps->maybeThinking) return;
13063         if (appData.debugMode)
13064           fprintf(debugFP, "Interrupting %s\n", cps->which);
13065         InterruptChildProcess(cps->pr);
13066         cps->maybeThinking = FALSE;
13067         break;
13068       default:
13069         break;
13070     }
13071 #endif /*ATTENTION*/
13072 }
13073
13074 int
13075 CheckFlags()
13076 {
13077     if (whiteTimeRemaining <= 0) {
13078         if (!whiteFlag) {
13079             whiteFlag = TRUE;
13080             if (appData.icsActive) {
13081                 if (appData.autoCallFlag &&
13082                     gameMode == IcsPlayingBlack && !blackFlag) {
13083                   SendToICS(ics_prefix);
13084                   SendToICS("flag\n");
13085                 }
13086             } else {
13087                 if (blackFlag) {
13088                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13089                 } else {
13090                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13091                     if (appData.autoCallFlag) {
13092                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13093                         return TRUE;
13094                     }
13095                 }
13096             }
13097         }
13098     }
13099     if (blackTimeRemaining <= 0) {
13100         if (!blackFlag) {
13101             blackFlag = TRUE;
13102             if (appData.icsActive) {
13103                 if (appData.autoCallFlag &&
13104                     gameMode == IcsPlayingWhite && !whiteFlag) {
13105                   SendToICS(ics_prefix);
13106                   SendToICS("flag\n");
13107                 }
13108             } else {
13109                 if (whiteFlag) {
13110                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13111                 } else {
13112                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13113                     if (appData.autoCallFlag) {
13114                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13115                         return TRUE;
13116                     }
13117                 }
13118             }
13119         }
13120     }
13121     return FALSE;
13122 }
13123
13124 void
13125 CheckTimeControl()
13126 {
13127     if (!appData.clockMode || appData.icsActive ||
13128         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13129
13130     /*
13131      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13132      */
13133     if ( !WhiteOnMove(forwardMostMove) )
13134         /* White made time control */
13135         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13136         /* [HGM] time odds: correct new time quota for time odds! */
13137                                             / WhitePlayer()->timeOdds;
13138       else
13139         /* Black made time control */
13140         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13141                                             / WhitePlayer()->other->timeOdds;
13142 }
13143
13144 void
13145 DisplayBothClocks()
13146 {
13147     int wom = gameMode == EditPosition ?
13148       !blackPlaysFirst : WhiteOnMove(currentMove);
13149     DisplayWhiteClock(whiteTimeRemaining, wom);
13150     DisplayBlackClock(blackTimeRemaining, !wom);
13151 }
13152
13153
13154 /* Timekeeping seems to be a portability nightmare.  I think everyone
13155    has ftime(), but I'm really not sure, so I'm including some ifdefs
13156    to use other calls if you don't.  Clocks will be less accurate if
13157    you have neither ftime nor gettimeofday.
13158 */
13159
13160 /* VS 2008 requires the #include outside of the function */
13161 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13162 #include <sys/timeb.h>
13163 #endif
13164
13165 /* Get the current time as a TimeMark */
13166 void
13167 GetTimeMark(tm)
13168      TimeMark *tm;
13169 {
13170 #if HAVE_GETTIMEOFDAY
13171
13172     struct timeval timeVal;
13173     struct timezone timeZone;
13174
13175     gettimeofday(&timeVal, &timeZone);
13176     tm->sec = (long) timeVal.tv_sec; 
13177     tm->ms = (int) (timeVal.tv_usec / 1000L);
13178
13179 #else /*!HAVE_GETTIMEOFDAY*/
13180 #if HAVE_FTIME
13181
13182 // include <sys/timeb.h> / moved to just above start of function
13183     struct timeb timeB;
13184
13185     ftime(&timeB);
13186     tm->sec = (long) timeB.time;
13187     tm->ms = (int) timeB.millitm;
13188
13189 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13190     tm->sec = (long) time(NULL);
13191     tm->ms = 0;
13192 #endif
13193 #endif
13194 }
13195
13196 /* Return the difference in milliseconds between two
13197    time marks.  We assume the difference will fit in a long!
13198 */
13199 long
13200 SubtractTimeMarks(tm2, tm1)
13201      TimeMark *tm2, *tm1;
13202 {
13203     return 1000L*(tm2->sec - tm1->sec) +
13204            (long) (tm2->ms - tm1->ms);
13205 }
13206
13207
13208 /*
13209  * Code to manage the game clocks.
13210  *
13211  * In tournament play, black starts the clock and then white makes a move.
13212  * We give the human user a slight advantage if he is playing white---the
13213  * clocks don't run until he makes his first move, so it takes zero time.
13214  * Also, we don't account for network lag, so we could get out of sync
13215  * with GNU Chess's clock -- but then, referees are always right.  
13216  */
13217
13218 static TimeMark tickStartTM;
13219 static long intendedTickLength;
13220
13221 long
13222 NextTickLength(timeRemaining)
13223      long timeRemaining;
13224 {
13225     long nominalTickLength, nextTickLength;
13226
13227     if (timeRemaining > 0L && timeRemaining <= 10000L)
13228       nominalTickLength = 100L;
13229     else
13230       nominalTickLength = 1000L;
13231     nextTickLength = timeRemaining % nominalTickLength;
13232     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13233
13234     return nextTickLength;
13235 }
13236
13237 /* Adjust clock one minute up or down */
13238 void
13239 AdjustClock(Boolean which, int dir)
13240 {
13241     if(which) blackTimeRemaining += 60000*dir;
13242     else      whiteTimeRemaining += 60000*dir;
13243     DisplayBothClocks();
13244 }
13245
13246 /* Stop clocks and reset to a fresh time control */
13247 void
13248 ResetClocks() 
13249 {
13250     (void) StopClockTimer();
13251     if (appData.icsActive) {
13252         whiteTimeRemaining = blackTimeRemaining = 0;
13253     } else { /* [HGM] correct new time quote for time odds */
13254         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13255         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13256     }
13257     if (whiteFlag || blackFlag) {
13258         DisplayTitle("");
13259         whiteFlag = blackFlag = FALSE;
13260     }
13261     DisplayBothClocks();
13262 }
13263
13264 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13265
13266 /* Decrement running clock by amount of time that has passed */
13267 void
13268 DecrementClocks()
13269 {
13270     long timeRemaining;
13271     long lastTickLength, fudge;
13272     TimeMark now;
13273
13274     if (!appData.clockMode) return;
13275     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13276         
13277     GetTimeMark(&now);
13278
13279     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13280
13281     /* Fudge if we woke up a little too soon */
13282     fudge = intendedTickLength - lastTickLength;
13283     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13284
13285     if (WhiteOnMove(forwardMostMove)) {
13286         if(whiteNPS >= 0) lastTickLength = 0;
13287         timeRemaining = whiteTimeRemaining -= lastTickLength;
13288         DisplayWhiteClock(whiteTimeRemaining - fudge,
13289                           WhiteOnMove(currentMove));
13290     } else {
13291         if(blackNPS >= 0) lastTickLength = 0;
13292         timeRemaining = blackTimeRemaining -= lastTickLength;
13293         DisplayBlackClock(blackTimeRemaining - fudge,
13294                           !WhiteOnMove(currentMove));
13295     }
13296
13297     if (CheckFlags()) return;
13298         
13299     tickStartTM = now;
13300     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13301     StartClockTimer(intendedTickLength);
13302
13303     /* if the time remaining has fallen below the alarm threshold, sound the
13304      * alarm. if the alarm has sounded and (due to a takeback or time control
13305      * with increment) the time remaining has increased to a level above the
13306      * threshold, reset the alarm so it can sound again. 
13307      */
13308     
13309     if (appData.icsActive && appData.icsAlarm) {
13310
13311         /* make sure we are dealing with the user's clock */
13312         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13313                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13314            )) return;
13315
13316         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13317             alarmSounded = FALSE;
13318         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13319             PlayAlarmSound();
13320             alarmSounded = TRUE;
13321         }
13322     }
13323 }
13324
13325
13326 /* A player has just moved, so stop the previously running
13327    clock and (if in clock mode) start the other one.
13328    We redisplay both clocks in case we're in ICS mode, because
13329    ICS gives us an update to both clocks after every move.
13330    Note that this routine is called *after* forwardMostMove
13331    is updated, so the last fractional tick must be subtracted
13332    from the color that is *not* on move now.
13333 */
13334 void
13335 SwitchClocks()
13336 {
13337     long lastTickLength;
13338     TimeMark now;
13339     int flagged = FALSE;
13340
13341     GetTimeMark(&now);
13342
13343     if (StopClockTimer() && appData.clockMode) {
13344         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13345         if (WhiteOnMove(forwardMostMove)) {
13346             if(blackNPS >= 0) lastTickLength = 0;
13347             blackTimeRemaining -= lastTickLength;
13348            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13349 //         if(pvInfoList[forwardMostMove-1].time == -1)
13350                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13351                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13352         } else {
13353            if(whiteNPS >= 0) lastTickLength = 0;
13354            whiteTimeRemaining -= lastTickLength;
13355            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13356 //         if(pvInfoList[forwardMostMove-1].time == -1)
13357                  pvInfoList[forwardMostMove-1].time = 
13358                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13359         }
13360         flagged = CheckFlags();
13361     }
13362     CheckTimeControl();
13363
13364     if (flagged || !appData.clockMode) return;
13365
13366     switch (gameMode) {
13367       case MachinePlaysBlack:
13368       case MachinePlaysWhite:
13369       case BeginningOfGame:
13370         if (pausing) return;
13371         break;
13372
13373       case EditGame:
13374       case PlayFromGameFile:
13375       case IcsExamining:
13376         return;
13377
13378       default:
13379         break;
13380     }
13381
13382     tickStartTM = now;
13383     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13384       whiteTimeRemaining : blackTimeRemaining);
13385     StartClockTimer(intendedTickLength);
13386 }
13387         
13388
13389 /* Stop both clocks */
13390 void
13391 StopClocks()
13392 {       
13393     long lastTickLength;
13394     TimeMark now;
13395
13396     if (!StopClockTimer()) return;
13397     if (!appData.clockMode) return;
13398
13399     GetTimeMark(&now);
13400
13401     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13402     if (WhiteOnMove(forwardMostMove)) {
13403         if(whiteNPS >= 0) lastTickLength = 0;
13404         whiteTimeRemaining -= lastTickLength;
13405         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13406     } else {
13407         if(blackNPS >= 0) lastTickLength = 0;
13408         blackTimeRemaining -= lastTickLength;
13409         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13410     }
13411     CheckFlags();
13412 }
13413         
13414 /* Start clock of player on move.  Time may have been reset, so
13415    if clock is already running, stop and restart it. */
13416 void
13417 StartClocks()
13418 {
13419     (void) StopClockTimer(); /* in case it was running already */
13420     DisplayBothClocks();
13421     if (CheckFlags()) return;
13422
13423     if (!appData.clockMode) return;
13424     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13425
13426     GetTimeMark(&tickStartTM);
13427     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13428       whiteTimeRemaining : blackTimeRemaining);
13429
13430    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13431     whiteNPS = blackNPS = -1; 
13432     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13433        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13434         whiteNPS = first.nps;
13435     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13436        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13437         blackNPS = first.nps;
13438     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13439         whiteNPS = second.nps;
13440     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13441         blackNPS = second.nps;
13442     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13443
13444     StartClockTimer(intendedTickLength);
13445 }
13446
13447 char *
13448 TimeString(ms)
13449      long ms;
13450 {
13451     long second, minute, hour, day;
13452     char *sign = "";
13453     static char buf[32];
13454     
13455     if (ms > 0 && ms <= 9900) {
13456       /* convert milliseconds to tenths, rounding up */
13457       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13458
13459       sprintf(buf, " %03.1f ", tenths/10.0);
13460       return buf;
13461     }
13462
13463     /* convert milliseconds to seconds, rounding up */
13464     /* use floating point to avoid strangeness of integer division
13465        with negative dividends on many machines */
13466     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13467
13468     if (second < 0) {
13469         sign = "-";
13470         second = -second;
13471     }
13472     
13473     day = second / (60 * 60 * 24);
13474     second = second % (60 * 60 * 24);
13475     hour = second / (60 * 60);
13476     second = second % (60 * 60);
13477     minute = second / 60;
13478     second = second % 60;
13479     
13480     if (day > 0)
13481       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13482               sign, day, hour, minute, second);
13483     else if (hour > 0)
13484       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13485     else
13486       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13487     
13488     return buf;
13489 }
13490
13491
13492 /*
13493  * This is necessary because some C libraries aren't ANSI C compliant yet.
13494  */
13495 char *
13496 StrStr(string, match)
13497      char *string, *match;
13498 {
13499     int i, length;
13500     
13501     length = strlen(match);
13502     
13503     for (i = strlen(string) - length; i >= 0; i--, string++)
13504       if (!strncmp(match, string, length))
13505         return string;
13506     
13507     return NULL;
13508 }
13509
13510 char *
13511 StrCaseStr(string, match)
13512      char *string, *match;
13513 {
13514     int i, j, length;
13515     
13516     length = strlen(match);
13517     
13518     for (i = strlen(string) - length; i >= 0; i--, string++) {
13519         for (j = 0; j < length; j++) {
13520             if (ToLower(match[j]) != ToLower(string[j]))
13521               break;
13522         }
13523         if (j == length) return string;
13524     }
13525
13526     return NULL;
13527 }
13528
13529 #ifndef _amigados
13530 int
13531 StrCaseCmp(s1, s2)
13532      char *s1, *s2;
13533 {
13534     char c1, c2;
13535     
13536     for (;;) {
13537         c1 = ToLower(*s1++);
13538         c2 = ToLower(*s2++);
13539         if (c1 > c2) return 1;
13540         if (c1 < c2) return -1;
13541         if (c1 == NULLCHAR) return 0;
13542     }
13543 }
13544
13545
13546 int
13547 ToLower(c)
13548      int c;
13549 {
13550     return isupper(c) ? tolower(c) : c;
13551 }
13552
13553
13554 int
13555 ToUpper(c)
13556      int c;
13557 {
13558     return islower(c) ? toupper(c) : c;
13559 }
13560 #endif /* !_amigados    */
13561
13562 char *
13563 StrSave(s)
13564      char *s;
13565 {
13566     char *ret;
13567
13568     if ((ret = (char *) malloc(strlen(s) + 1))) {
13569         strcpy(ret, s);
13570     }
13571     return ret;
13572 }
13573
13574 char *
13575 StrSavePtr(s, savePtr)
13576      char *s, **savePtr;
13577 {
13578     if (*savePtr) {
13579         free(*savePtr);
13580     }
13581     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13582         strcpy(*savePtr, s);
13583     }
13584     return(*savePtr);
13585 }
13586
13587 char *
13588 PGNDate()
13589 {
13590     time_t clock;
13591     struct tm *tm;
13592     char buf[MSG_SIZ];
13593
13594     clock = time((time_t *)NULL);
13595     tm = localtime(&clock);
13596     sprintf(buf, "%04d.%02d.%02d",
13597             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13598     return StrSave(buf);
13599 }
13600
13601
13602 char *
13603 PositionToFEN(move, overrideCastling)
13604      int move;
13605      char *overrideCastling;
13606 {
13607     int i, j, fromX, fromY, toX, toY;
13608     int whiteToPlay;
13609     char buf[128];
13610     char *p, *q;
13611     int emptycount;
13612     ChessSquare piece;
13613
13614     whiteToPlay = (gameMode == EditPosition) ?
13615       !blackPlaysFirst : (move % 2 == 0);
13616     p = buf;
13617
13618     /* Piece placement data */
13619     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13620         emptycount = 0;
13621         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13622             if (boards[move][i][j] == EmptySquare) {
13623                 emptycount++;
13624             } else { ChessSquare piece = boards[move][i][j];
13625                 if (emptycount > 0) {
13626                     if(emptycount<10) /* [HGM] can be >= 10 */
13627                         *p++ = '0' + emptycount;
13628                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13629                     emptycount = 0;
13630                 }
13631                 if(PieceToChar(piece) == '+') {
13632                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13633                     *p++ = '+';
13634                     piece = (ChessSquare)(DEMOTED piece);
13635                 } 
13636                 *p++ = PieceToChar(piece);
13637                 if(p[-1] == '~') {
13638                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13639                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13640                     *p++ = '~';
13641                 }
13642             }
13643         }
13644         if (emptycount > 0) {
13645             if(emptycount<10) /* [HGM] can be >= 10 */
13646                 *p++ = '0' + emptycount;
13647             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13648             emptycount = 0;
13649         }
13650         *p++ = '/';
13651     }
13652     *(p - 1) = ' ';
13653
13654     /* [HGM] print Crazyhouse or Shogi holdings */
13655     if( gameInfo.holdingsWidth ) {
13656         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13657         q = p;
13658         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13659             piece = boards[move][i][BOARD_WIDTH-1];
13660             if( piece != EmptySquare )
13661               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13662                   *p++ = PieceToChar(piece);
13663         }
13664         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13665             piece = boards[move][BOARD_HEIGHT-i-1][0];
13666             if( piece != EmptySquare )
13667               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13668                   *p++ = PieceToChar(piece);
13669         }
13670
13671         if( q == p ) *p++ = '-';
13672         *p++ = ']';
13673         *p++ = ' ';
13674     }
13675
13676     /* Active color */
13677     *p++ = whiteToPlay ? 'w' : 'b';
13678     *p++ = ' ';
13679
13680   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13681     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13682   } else {
13683   if(nrCastlingRights) {
13684      q = p;
13685      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13686        /* [HGM] write directly from rights */
13687            if(castlingRights[move][2] >= 0 &&
13688               castlingRights[move][0] >= 0   )
13689                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13690            if(castlingRights[move][2] >= 0 &&
13691               castlingRights[move][1] >= 0   )
13692                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13693            if(castlingRights[move][5] >= 0 &&
13694               castlingRights[move][3] >= 0   )
13695                 *p++ = castlingRights[move][3] + AAA;
13696            if(castlingRights[move][5] >= 0 &&
13697               castlingRights[move][4] >= 0   )
13698                 *p++ = castlingRights[move][4] + AAA;
13699      } else {
13700
13701         /* [HGM] write true castling rights */
13702         if( nrCastlingRights == 6 ) {
13703             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13704                castlingRights[move][2] >= 0  ) *p++ = 'K';
13705             if(castlingRights[move][1] == BOARD_LEFT &&
13706                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13707             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13708                castlingRights[move][5] >= 0  ) *p++ = 'k';
13709             if(castlingRights[move][4] == BOARD_LEFT &&
13710                castlingRights[move][5] >= 0  ) *p++ = 'q';
13711         }
13712      }
13713      if (q == p) *p++ = '-'; /* No castling rights */
13714      *p++ = ' ';
13715   }
13716
13717   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13718      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13719     /* En passant target square */
13720     if (move > backwardMostMove) {
13721         fromX = moveList[move - 1][0] - AAA;
13722         fromY = moveList[move - 1][1] - ONE;
13723         toX = moveList[move - 1][2] - AAA;
13724         toY = moveList[move - 1][3] - ONE;
13725         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13726             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13727             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13728             fromX == toX) {
13729             /* 2-square pawn move just happened */
13730             *p++ = toX + AAA;
13731             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13732         } else {
13733             *p++ = '-';
13734         }
13735     } else if(move == backwardMostMove) {
13736         // [HGM] perhaps we should always do it like this, and forget the above?
13737         if(epStatus[move] >= 0) {
13738             *p++ = epStatus[move] + AAA;
13739             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13740         } else {
13741             *p++ = '-';
13742         }
13743     } else {
13744         *p++ = '-';
13745     }
13746     *p++ = ' ';
13747   }
13748   }
13749
13750     /* [HGM] find reversible plies */
13751     {   int i = 0, j=move;
13752
13753         if (appData.debugMode) { int k;
13754             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13755             for(k=backwardMostMove; k<=forwardMostMove; k++)
13756                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13757
13758         }
13759
13760         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13761         if( j == backwardMostMove ) i += initialRulePlies;
13762         sprintf(p, "%d ", i);
13763         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13764     }
13765     /* Fullmove number */
13766     sprintf(p, "%d", (move / 2) + 1);
13767     
13768     return StrSave(buf);
13769 }
13770
13771 Boolean
13772 ParseFEN(board, blackPlaysFirst, fen)
13773     Board board;
13774      int *blackPlaysFirst;
13775      char *fen;
13776 {
13777     int i, j;
13778     char *p;
13779     int emptycount;
13780     ChessSquare piece;
13781
13782     p = fen;
13783
13784     /* [HGM] by default clear Crazyhouse holdings, if present */
13785     if(gameInfo.holdingsWidth) {
13786        for(i=0; i<BOARD_HEIGHT; i++) {
13787            board[i][0]             = EmptySquare; /* black holdings */
13788            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13789            board[i][1]             = (ChessSquare) 0; /* black counts */
13790            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13791        }
13792     }
13793
13794     /* Piece placement data */
13795     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13796         j = 0;
13797         for (;;) {
13798             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13799                 if (*p == '/') p++;
13800                 emptycount = gameInfo.boardWidth - j;
13801                 while (emptycount--)
13802                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13803                 break;
13804 #if(BOARD_SIZE >= 10)
13805             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13806                 p++; emptycount=10;
13807                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13808                 while (emptycount--)
13809                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13810 #endif
13811             } else if (isdigit(*p)) {
13812                 emptycount = *p++ - '0';
13813                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13814                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13815                 while (emptycount--)
13816                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13817             } else if (*p == '+' || isalpha(*p)) {
13818                 if (j >= gameInfo.boardWidth) return FALSE;
13819                 if(*p=='+') {
13820                     piece = CharToPiece(*++p);
13821                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13822                     piece = (ChessSquare) (PROMOTED piece ); p++;
13823                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13824                 } else piece = CharToPiece(*p++);
13825
13826                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13827                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13828                     piece = (ChessSquare) (PROMOTED piece);
13829                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13830                     p++;
13831                 }
13832                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13833             } else {
13834                 return FALSE;
13835             }
13836         }
13837     }
13838     while (*p == '/' || *p == ' ') p++;
13839
13840     /* [HGM] look for Crazyhouse holdings here */
13841     while(*p==' ') p++;
13842     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13843         if(*p == '[') p++;
13844         if(*p == '-' ) *p++; /* empty holdings */ else {
13845             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13846             /* if we would allow FEN reading to set board size, we would   */
13847             /* have to add holdings and shift the board read so far here   */
13848             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13849                 *p++;
13850                 if((int) piece >= (int) BlackPawn ) {
13851                     i = (int)piece - (int)BlackPawn;
13852                     i = PieceToNumber((ChessSquare)i);
13853                     if( i >= gameInfo.holdingsSize ) return FALSE;
13854                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13855                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13856                 } else {
13857                     i = (int)piece - (int)WhitePawn;
13858                     i = PieceToNumber((ChessSquare)i);
13859                     if( i >= gameInfo.holdingsSize ) return FALSE;
13860                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13861                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13862                 }
13863             }
13864         }
13865         if(*p == ']') *p++;
13866     }
13867
13868     while(*p == ' ') p++;
13869
13870     /* Active color */
13871     switch (*p++) {
13872       case 'w':
13873         *blackPlaysFirst = FALSE;
13874         break;
13875       case 'b': 
13876         *blackPlaysFirst = TRUE;
13877         break;
13878       default:
13879         return FALSE;
13880     }
13881
13882     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13883     /* return the extra info in global variiables             */
13884
13885     /* set defaults in case FEN is incomplete */
13886     FENepStatus = EP_UNKNOWN;
13887     for(i=0; i<nrCastlingRights; i++ ) {
13888         FENcastlingRights[i] =
13889             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13890     }   /* assume possible unless obviously impossible */
13891     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13892     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13893     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13894     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13895     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13896     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13897     FENrulePlies = 0;
13898
13899     while(*p==' ') p++;
13900     if(nrCastlingRights) {
13901       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13902           /* castling indicator present, so default becomes no castlings */
13903           for(i=0; i<nrCastlingRights; i++ ) {
13904                  FENcastlingRights[i] = -1;
13905           }
13906       }
13907       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13908              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13909              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13910              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13911         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13912
13913         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13914             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13915             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13916         }
13917         switch(c) {
13918           case'K':
13919               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13920               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13921               FENcastlingRights[2] = whiteKingFile;
13922               break;
13923           case'Q':
13924               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13925               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13926               FENcastlingRights[2] = whiteKingFile;
13927               break;
13928           case'k':
13929               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13930               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13931               FENcastlingRights[5] = blackKingFile;
13932               break;
13933           case'q':
13934               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13935               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13936               FENcastlingRights[5] = blackKingFile;
13937           case '-':
13938               break;
13939           default: /* FRC castlings */
13940               if(c >= 'a') { /* black rights */
13941                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13942                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13943                   if(i == BOARD_RGHT) break;
13944                   FENcastlingRights[5] = i;
13945                   c -= AAA;
13946                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13947                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13948                   if(c > i)
13949                       FENcastlingRights[3] = c;
13950                   else
13951                       FENcastlingRights[4] = c;
13952               } else { /* white rights */
13953                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13954                     if(board[0][i] == WhiteKing) break;
13955                   if(i == BOARD_RGHT) break;
13956                   FENcastlingRights[2] = i;
13957                   c -= AAA - 'a' + 'A';
13958                   if(board[0][c] >= WhiteKing) break;
13959                   if(c > i)
13960                       FENcastlingRights[0] = c;
13961                   else
13962                       FENcastlingRights[1] = c;
13963               }
13964         }
13965       }
13966     if (appData.debugMode) {
13967         fprintf(debugFP, "FEN castling rights:");
13968         for(i=0; i<nrCastlingRights; i++)
13969         fprintf(debugFP, " %d", FENcastlingRights[i]);
13970         fprintf(debugFP, "\n");
13971     }
13972
13973       while(*p==' ') p++;
13974     }
13975
13976     /* read e.p. field in games that know e.p. capture */
13977     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13978        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13979       if(*p=='-') {
13980         p++; FENepStatus = EP_NONE;
13981       } else {
13982          char c = *p++ - AAA;
13983
13984          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13985          if(*p >= '0' && *p <='9') *p++;
13986          FENepStatus = c;
13987       }
13988     }
13989
13990
13991     if(sscanf(p, "%d", &i) == 1) {
13992         FENrulePlies = i; /* 50-move ply counter */
13993         /* (The move number is still ignored)    */
13994     }
13995
13996     return TRUE;
13997 }
13998       
13999 void
14000 EditPositionPasteFEN(char *fen)
14001 {
14002   if (fen != NULL) {
14003     Board initial_position;
14004
14005     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14006       DisplayError(_("Bad FEN position in clipboard"), 0);
14007       return ;
14008     } else {
14009       int savedBlackPlaysFirst = blackPlaysFirst;
14010       EditPositionEvent();
14011       blackPlaysFirst = savedBlackPlaysFirst;
14012       CopyBoard(boards[0], initial_position);
14013           /* [HGM] copy FEN attributes as well */
14014           {   int i;
14015               initialRulePlies = FENrulePlies;
14016               epStatus[0] = FENepStatus;
14017               for( i=0; i<nrCastlingRights; i++ )
14018                   castlingRights[0][i] = FENcastlingRights[i];
14019           }
14020       EditPositionDone(FALSE);
14021       DisplayBothClocks();
14022       DrawPosition(FALSE, boards[currentMove]);
14023     }
14024   }
14025 }
14026
14027 static char cseq[12] = "\\   ";
14028
14029 Boolean set_cont_sequence(char *new_seq)
14030 {
14031     int len;
14032     Boolean ret;
14033
14034     // handle bad attempts to set the sequence
14035         if (!new_seq)
14036                 return 0; // acceptable error - no debug
14037
14038     len = strlen(new_seq);
14039     ret = (len > 0) && (len < sizeof(cseq));
14040     if (ret)
14041         strcpy(cseq, new_seq);
14042     else if (appData.debugMode)
14043         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14044     return ret;
14045 }
14046
14047 /*
14048     reformat a source message so words don't cross the width boundary.  internal
14049     newlines are not removed.  returns the wrapped size (no null character unless
14050     included in source message).  If dest is NULL, only calculate the size required
14051     for the dest buffer.  lp argument indicats line position upon entry, and it's
14052     passed back upon exit.
14053 */
14054 int wrap(char *dest, char *src, int count, int width, int *lp)
14055 {
14056     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14057
14058     cseq_len = strlen(cseq);
14059     old_line = line = *lp;
14060     ansi = len = clen = 0;
14061
14062     for (i=0; i < count; i++)
14063     {
14064         if (src[i] == '\033')
14065             ansi = 1;
14066
14067         // if we hit the width, back up
14068         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14069         {
14070             // store i & len in case the word is too long
14071             old_i = i, old_len = len;
14072
14073             // find the end of the last word
14074             while (i && src[i] != ' ' && src[i] != '\n')
14075             {
14076                 i--;
14077                 len--;
14078             }
14079
14080             // word too long?  restore i & len before splitting it
14081             if ((old_i-i+clen) >= width)
14082             {
14083                 i = old_i;
14084                 len = old_len;
14085             }
14086
14087             // extra space?
14088             if (i && src[i-1] == ' ')
14089                 len--;
14090
14091             if (src[i] != ' ' && src[i] != '\n')
14092             {
14093                 i--;
14094                 if (len)
14095                     len--;
14096             }
14097
14098             // now append the newline and continuation sequence
14099             if (dest)
14100                 dest[len] = '\n';
14101             len++;
14102             if (dest)
14103                 strncpy(dest+len, cseq, cseq_len);
14104             len += cseq_len;
14105             line = cseq_len;
14106             clen = cseq_len;
14107             continue;
14108         }
14109
14110         if (dest)
14111             dest[len] = src[i];
14112         len++;
14113         if (!ansi)
14114             line++;
14115         if (src[i] == '\n')
14116             line = 0;
14117         if (src[i] == 'm')
14118             ansi = 0;
14119     }
14120     if (dest && appData.debugMode)
14121     {
14122         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14123             count, width, line, len, *lp);
14124         show_bytes(debugFP, src, count);
14125         fprintf(debugFP, "\ndest: ");
14126         show_bytes(debugFP, dest, len);
14127         fprintf(debugFP, "\n");
14128     }
14129     *lp = dest ? line : old_line;
14130
14131     return len;
14132 }