release of version 4.4.2
[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 (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
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 ||
6928                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6929                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6930                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6931                      !WhiteOnMove(currentMove)
6932                     ) )
6933                 {
6934                     curscore = -curscore;
6935                 }
6936
6937
6938                 programStats.depth = plylev;
6939                 programStats.nodes = nodes;
6940                 programStats.time = time;
6941                 programStats.score = curscore;
6942                 programStats.got_only_move = 0;
6943
6944                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6945                         int ticklen;
6946
6947                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6948                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6949                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6950                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6951                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6952                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6953                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6954                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6955                 }
6956
6957                 /* Buffer overflow protection */
6958                 if (buf1[0] != NULLCHAR) {
6959                     if (strlen(buf1) >= sizeof(programStats.movelist)
6960                         && appData.debugMode) {
6961                         fprintf(debugFP,
6962                                 "PV is too long; using the first %u bytes.\n",
6963                                 (unsigned) sizeof(programStats.movelist) - 1);
6964                     }
6965
6966                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6967                 } else {
6968                     sprintf(programStats.movelist, " no PV\n");
6969                 }
6970
6971                 if (programStats.seen_stat) {
6972                     programStats.ok_to_send = 1;
6973                 }
6974
6975                 if (strchr(programStats.movelist, '(') != NULL) {
6976                     programStats.line_is_book = 1;
6977                     programStats.nr_moves = 0;
6978                     programStats.moves_left = 0;
6979                 } else {
6980                     programStats.line_is_book = 0;
6981                 }
6982
6983                 SendProgramStatsToFrontend( cps, &programStats );
6984
6985                 /* 
6986                     [AS] Protect the thinkOutput buffer from overflow... this
6987                     is only useful if buf1 hasn't overflowed first!
6988                 */
6989                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6990                         plylev, 
6991                         (gameMode == TwoMachinesPlay ?
6992                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6993                         ((double) curscore) / 100.0,
6994                         prefixHint ? lastHint : "",
6995                         prefixHint ? " " : "" );
6996
6997                 if( buf1[0] != NULLCHAR ) {
6998                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6999
7000                     if( strlen(buf1) > max_len ) {
7001                         if( appData.debugMode) {
7002                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7003                         }
7004                         buf1[max_len+1] = '\0';
7005                     }
7006
7007                     strcat( thinkOutput, buf1 );
7008                 }
7009
7010                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7011                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7012                     DisplayMove(currentMove - 1);
7013                 }
7014                 return;
7015
7016             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7017                 /* crafty (9.25+) says "(only move) <move>"
7018                  * if there is only 1 legal move
7019                  */
7020                 sscanf(p, "(only move) %s", buf1);
7021                 sprintf(thinkOutput, "%s (only move)", buf1);
7022                 sprintf(programStats.movelist, "%s (only move)", buf1);
7023                 programStats.depth = 1;
7024                 programStats.nr_moves = 1;
7025                 programStats.moves_left = 1;
7026                 programStats.nodes = 1;
7027                 programStats.time = 1;
7028                 programStats.got_only_move = 1;
7029
7030                 /* Not really, but we also use this member to
7031                    mean "line isn't going to change" (Crafty
7032                    isn't searching, so stats won't change) */
7033                 programStats.line_is_book = 1;
7034
7035                 SendProgramStatsToFrontend( cps, &programStats );
7036                 
7037                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7038                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7039                     DisplayMove(currentMove - 1);
7040                 }
7041                 return;
7042             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7043                               &time, &nodes, &plylev, &mvleft,
7044                               &mvtot, mvname) >= 5) {
7045                 /* The stat01: line is from Crafty (9.29+) in response
7046                    to the "." command */
7047                 programStats.seen_stat = 1;
7048                 cps->maybeThinking = TRUE;
7049
7050                 if (programStats.got_only_move || !appData.periodicUpdates)
7051                   return;
7052
7053                 programStats.depth = plylev;
7054                 programStats.time = time;
7055                 programStats.nodes = nodes;
7056                 programStats.moves_left = mvleft;
7057                 programStats.nr_moves = mvtot;
7058                 strcpy(programStats.move_name, mvname);
7059                 programStats.ok_to_send = 1;
7060                 programStats.movelist[0] = '\0';
7061
7062                 SendProgramStatsToFrontend( cps, &programStats );
7063
7064                 return;
7065
7066             } else if (strncmp(message,"++",2) == 0) {
7067                 /* Crafty 9.29+ outputs this */
7068                 programStats.got_fail = 2;
7069                 return;
7070
7071             } else if (strncmp(message,"--",2) == 0) {
7072                 /* Crafty 9.29+ outputs this */
7073                 programStats.got_fail = 1;
7074                 return;
7075
7076             } else if (thinkOutput[0] != NULLCHAR &&
7077                        strncmp(message, "    ", 4) == 0) {
7078                 unsigned message_len;
7079
7080                 p = message;
7081                 while (*p && *p == ' ') p++;
7082
7083                 message_len = strlen( p );
7084
7085                 /* [AS] Avoid buffer overflow */
7086                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7087                     strcat(thinkOutput, " ");
7088                     strcat(thinkOutput, p);
7089                 }
7090
7091                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7092                     strcat(programStats.movelist, " ");
7093                     strcat(programStats.movelist, p);
7094                 }
7095
7096                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7097                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7098                     DisplayMove(currentMove - 1);
7099                 }
7100                 return;
7101             }
7102         }
7103         else {
7104             buf1[0] = NULLCHAR;
7105
7106             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7107                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7108             {
7109                 ChessProgramStats cpstats;
7110
7111                 if (plyext != ' ' && plyext != '\t') {
7112                     time *= 100;
7113                 }
7114
7115                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7116                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7117                     curscore = -curscore;
7118                 }
7119
7120                 cpstats.depth = plylev;
7121                 cpstats.nodes = nodes;
7122                 cpstats.time = time;
7123                 cpstats.score = curscore;
7124                 cpstats.got_only_move = 0;
7125                 cpstats.movelist[0] = '\0';
7126
7127                 if (buf1[0] != NULLCHAR) {
7128                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7129                 }
7130
7131                 cpstats.ok_to_send = 0;
7132                 cpstats.line_is_book = 0;
7133                 cpstats.nr_moves = 0;
7134                 cpstats.moves_left = 0;
7135
7136                 SendProgramStatsToFrontend( cps, &cpstats );
7137             }
7138         }
7139     }
7140 }
7141
7142
7143 /* Parse a game score from the character string "game", and
7144    record it as the history of the current game.  The game
7145    score is NOT assumed to start from the standard position. 
7146    The display is not updated in any way.
7147    */
7148 void
7149 ParseGameHistory(game)
7150      char *game;
7151 {
7152     ChessMove moveType;
7153     int fromX, fromY, toX, toY, boardIndex;
7154     char promoChar;
7155     char *p, *q;
7156     char buf[MSG_SIZ];
7157
7158     if (appData.debugMode)
7159       fprintf(debugFP, "Parsing game history: %s\n", game);
7160
7161     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7162     gameInfo.site = StrSave(appData.icsHost);
7163     gameInfo.date = PGNDate();
7164     gameInfo.round = StrSave("-");
7165
7166     /* Parse out names of players */
7167     while (*game == ' ') game++;
7168     p = buf;
7169     while (*game != ' ') *p++ = *game++;
7170     *p = NULLCHAR;
7171     gameInfo.white = StrSave(buf);
7172     while (*game == ' ') game++;
7173     p = buf;
7174     while (*game != ' ' && *game != '\n') *p++ = *game++;
7175     *p = NULLCHAR;
7176     gameInfo.black = StrSave(buf);
7177
7178     /* Parse moves */
7179     boardIndex = blackPlaysFirst ? 1 : 0;
7180     yynewstr(game);
7181     for (;;) {
7182         yyboardindex = boardIndex;
7183         moveType = (ChessMove) yylex();
7184         switch (moveType) {
7185           case IllegalMove:             /* maybe suicide chess, etc. */
7186   if (appData.debugMode) {
7187     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7188     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7189     setbuf(debugFP, NULL);
7190   }
7191           case WhitePromotionChancellor:
7192           case BlackPromotionChancellor:
7193           case WhitePromotionArchbishop:
7194           case BlackPromotionArchbishop:
7195           case WhitePromotionQueen:
7196           case BlackPromotionQueen:
7197           case WhitePromotionRook:
7198           case BlackPromotionRook:
7199           case WhitePromotionBishop:
7200           case BlackPromotionBishop:
7201           case WhitePromotionKnight:
7202           case BlackPromotionKnight:
7203           case WhitePromotionKing:
7204           case BlackPromotionKing:
7205           case NormalMove:
7206           case WhiteCapturesEnPassant:
7207           case BlackCapturesEnPassant:
7208           case WhiteKingSideCastle:
7209           case WhiteQueenSideCastle:
7210           case BlackKingSideCastle:
7211           case BlackQueenSideCastle:
7212           case WhiteKingSideCastleWild:
7213           case WhiteQueenSideCastleWild:
7214           case BlackKingSideCastleWild:
7215           case BlackQueenSideCastleWild:
7216           /* PUSH Fabien */
7217           case WhiteHSideCastleFR:
7218           case WhiteASideCastleFR:
7219           case BlackHSideCastleFR:
7220           case BlackASideCastleFR:
7221           /* POP Fabien */
7222             fromX = currentMoveString[0] - AAA;
7223             fromY = currentMoveString[1] - ONE;
7224             toX = currentMoveString[2] - AAA;
7225             toY = currentMoveString[3] - ONE;
7226             promoChar = currentMoveString[4];
7227             break;
7228           case WhiteDrop:
7229           case BlackDrop:
7230             fromX = moveType == WhiteDrop ?
7231               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7232             (int) CharToPiece(ToLower(currentMoveString[0]));
7233             fromY = DROP_RANK;
7234             toX = currentMoveString[2] - AAA;
7235             toY = currentMoveString[3] - ONE;
7236             promoChar = NULLCHAR;
7237             break;
7238           case AmbiguousMove:
7239             /* bug? */
7240             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7241   if (appData.debugMode) {
7242     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7243     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7244     setbuf(debugFP, NULL);
7245   }
7246             DisplayError(buf, 0);
7247             return;
7248           case ImpossibleMove:
7249             /* bug? */
7250             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7251   if (appData.debugMode) {
7252     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7253     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7254     setbuf(debugFP, NULL);
7255   }
7256             DisplayError(buf, 0);
7257             return;
7258           case (ChessMove) 0:   /* end of file */
7259             if (boardIndex < backwardMostMove) {
7260                 /* Oops, gap.  How did that happen? */
7261                 DisplayError(_("Gap in move list"), 0);
7262                 return;
7263             }
7264             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7265             if (boardIndex > forwardMostMove) {
7266                 forwardMostMove = boardIndex;
7267             }
7268             return;
7269           case ElapsedTime:
7270             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7271                 strcat(parseList[boardIndex-1], " ");
7272                 strcat(parseList[boardIndex-1], yy_text);
7273             }
7274             continue;
7275           case Comment:
7276           case PGNTag:
7277           case NAG:
7278           default:
7279             /* ignore */
7280             continue;
7281           case WhiteWins:
7282           case BlackWins:
7283           case GameIsDrawn:
7284           case GameUnfinished:
7285             if (gameMode == IcsExamining) {
7286                 if (boardIndex < backwardMostMove) {
7287                     /* Oops, gap.  How did that happen? */
7288                     return;
7289                 }
7290                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7291                 return;
7292             }
7293             gameInfo.result = moveType;
7294             p = strchr(yy_text, '{');
7295             if (p == NULL) p = strchr(yy_text, '(');
7296             if (p == NULL) {
7297                 p = yy_text;
7298                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7299             } else {
7300                 q = strchr(p, *p == '{' ? '}' : ')');
7301                 if (q != NULL) *q = NULLCHAR;
7302                 p++;
7303             }
7304             gameInfo.resultDetails = StrSave(p);
7305             continue;
7306         }
7307         if (boardIndex >= forwardMostMove &&
7308             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7309             backwardMostMove = blackPlaysFirst ? 1 : 0;
7310             return;
7311         }
7312         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7313                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7314                                  parseList[boardIndex]);
7315         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7316         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7317         /* currentMoveString is set as a side-effect of yylex */
7318         strcpy(moveList[boardIndex], currentMoveString);
7319         strcat(moveList[boardIndex], "\n");
7320         boardIndex++;
7321         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7322                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7323         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7324                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7325           case MT_NONE:
7326           case MT_STALEMATE:
7327           default:
7328             break;
7329           case MT_CHECK:
7330             if(gameInfo.variant != VariantShogi)
7331                 strcat(parseList[boardIndex - 1], "+");
7332             break;
7333           case MT_CHECKMATE:
7334           case MT_STAINMATE:
7335             strcat(parseList[boardIndex - 1], "#");
7336             break;
7337         }
7338     }
7339 }
7340
7341
7342 /* Apply a move to the given board  */
7343 void
7344 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7345      int fromX, fromY, toX, toY;
7346      int promoChar;
7347      Board board;
7348      char *castling;
7349      char *ep;
7350 {
7351   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7352
7353     /* [HGM] compute & store e.p. status and castling rights for new position */
7354     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7355     { int i;
7356
7357       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7358       oldEP = *ep;
7359       *ep = EP_NONE;
7360
7361       if( board[toY][toX] != EmptySquare ) 
7362            *ep = EP_CAPTURE;  
7363
7364       if( board[fromY][fromX] == WhitePawn ) {
7365            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7366                *ep = EP_PAWN_MOVE;
7367            if( toY-fromY==2) {
7368                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7369                         gameInfo.variant != VariantBerolina || toX < fromX)
7370                       *ep = toX | berolina;
7371                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7372                         gameInfo.variant != VariantBerolina || toX > fromX) 
7373                       *ep = toX;
7374            }
7375       } else 
7376       if( board[fromY][fromX] == BlackPawn ) {
7377            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7378                *ep = EP_PAWN_MOVE; 
7379            if( toY-fromY== -2) {
7380                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7381                         gameInfo.variant != VariantBerolina || toX < fromX)
7382                       *ep = toX | berolina;
7383                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7384                         gameInfo.variant != VariantBerolina || toX > fromX) 
7385                       *ep = toX;
7386            }
7387        }
7388
7389        for(i=0; i<nrCastlingRights; i++) {
7390            if(castling[i] == fromX && castlingRank[i] == fromY ||
7391               castling[i] == toX   && castlingRank[i] == toY   
7392              ) castling[i] = -1; // revoke for moved or captured piece
7393        }
7394
7395     }
7396
7397   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7398   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7399        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7400          
7401   if (fromX == toX && fromY == toY) return;
7402
7403   if (fromY == DROP_RANK) {
7404         /* must be first */
7405         piece = board[toY][toX] = (ChessSquare) fromX;
7406   } else {
7407      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7408      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7409      if(gameInfo.variant == VariantKnightmate)
7410          king += (int) WhiteUnicorn - (int) WhiteKing;
7411
7412     /* Code added by Tord: */
7413     /* FRC castling assumed when king captures friendly rook. */
7414     if (board[fromY][fromX] == WhiteKing &&
7415              board[toY][toX] == WhiteRook) {
7416       board[fromY][fromX] = EmptySquare;
7417       board[toY][toX] = EmptySquare;
7418       if(toX > fromX) {
7419         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7420       } else {
7421         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7422       }
7423     } else if (board[fromY][fromX] == BlackKing &&
7424                board[toY][toX] == BlackRook) {
7425       board[fromY][fromX] = EmptySquare;
7426       board[toY][toX] = EmptySquare;
7427       if(toX > fromX) {
7428         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7429       } else {
7430         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7431       }
7432     /* End of code added by Tord */
7433
7434     } else if (board[fromY][fromX] == king
7435         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7436         && toY == fromY && toX > fromX+1) {
7437         board[fromY][fromX] = EmptySquare;
7438         board[toY][toX] = king;
7439         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7440         board[fromY][BOARD_RGHT-1] = EmptySquare;
7441     } else if (board[fromY][fromX] == king
7442         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7443                && toY == fromY && toX < fromX-1) {
7444         board[fromY][fromX] = EmptySquare;
7445         board[toY][toX] = king;
7446         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7447         board[fromY][BOARD_LEFT] = EmptySquare;
7448     } else if (board[fromY][fromX] == WhitePawn
7449                && toY == BOARD_HEIGHT-1
7450                && gameInfo.variant != VariantXiangqi
7451                ) {
7452         /* white pawn promotion */
7453         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7454         if (board[toY][toX] == EmptySquare) {
7455             board[toY][toX] = WhiteQueen;
7456         }
7457         if(gameInfo.variant==VariantBughouse ||
7458            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7459             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7460         board[fromY][fromX] = EmptySquare;
7461     } else if ((fromY == BOARD_HEIGHT-4)
7462                && (toX != fromX)
7463                && gameInfo.variant != VariantXiangqi
7464                && gameInfo.variant != VariantBerolina
7465                && (board[fromY][fromX] == WhitePawn)
7466                && (board[toY][toX] == EmptySquare)) {
7467         board[fromY][fromX] = EmptySquare;
7468         board[toY][toX] = WhitePawn;
7469         captured = board[toY - 1][toX];
7470         board[toY - 1][toX] = EmptySquare;
7471     } else if ((fromY == BOARD_HEIGHT-4)
7472                && (toX == fromX)
7473                && gameInfo.variant == VariantBerolina
7474                && (board[fromY][fromX] == WhitePawn)
7475                && (board[toY][toX] == EmptySquare)) {
7476         board[fromY][fromX] = EmptySquare;
7477         board[toY][toX] = WhitePawn;
7478         if(oldEP & EP_BEROLIN_A) {
7479                 captured = board[fromY][fromX-1];
7480                 board[fromY][fromX-1] = EmptySquare;
7481         }else{  captured = board[fromY][fromX+1];
7482                 board[fromY][fromX+1] = EmptySquare;
7483         }
7484     } else if (board[fromY][fromX] == king
7485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7486                && toY == fromY && toX > fromX+1) {
7487         board[fromY][fromX] = EmptySquare;
7488         board[toY][toX] = king;
7489         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7490         board[fromY][BOARD_RGHT-1] = EmptySquare;
7491     } else if (board[fromY][fromX] == king
7492         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7493                && toY == fromY && toX < fromX-1) {
7494         board[fromY][fromX] = EmptySquare;
7495         board[toY][toX] = king;
7496         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7497         board[fromY][BOARD_LEFT] = EmptySquare;
7498     } else if (fromY == 7 && fromX == 3
7499                && board[fromY][fromX] == BlackKing
7500                && toY == 7 && toX == 5) {
7501         board[fromY][fromX] = EmptySquare;
7502         board[toY][toX] = BlackKing;
7503         board[fromY][7] = EmptySquare;
7504         board[toY][4] = BlackRook;
7505     } else if (fromY == 7 && fromX == 3
7506                && board[fromY][fromX] == BlackKing
7507                && toY == 7 && toX == 1) {
7508         board[fromY][fromX] = EmptySquare;
7509         board[toY][toX] = BlackKing;
7510         board[fromY][0] = EmptySquare;
7511         board[toY][2] = BlackRook;
7512     } else if (board[fromY][fromX] == BlackPawn
7513                && toY == 0
7514                && gameInfo.variant != VariantXiangqi
7515                ) {
7516         /* black pawn promotion */
7517         board[0][toX] = CharToPiece(ToLower(promoChar));
7518         if (board[0][toX] == EmptySquare) {
7519             board[0][toX] = BlackQueen;
7520         }
7521         if(gameInfo.variant==VariantBughouse ||
7522            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7523             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7524         board[fromY][fromX] = EmptySquare;
7525     } else if ((fromY == 3)
7526                && (toX != fromX)
7527                && gameInfo.variant != VariantXiangqi
7528                && gameInfo.variant != VariantBerolina
7529                && (board[fromY][fromX] == BlackPawn)
7530                && (board[toY][toX] == EmptySquare)) {
7531         board[fromY][fromX] = EmptySquare;
7532         board[toY][toX] = BlackPawn;
7533         captured = board[toY + 1][toX];
7534         board[toY + 1][toX] = EmptySquare;
7535     } else if ((fromY == 3)
7536                && (toX == fromX)
7537                && gameInfo.variant == VariantBerolina
7538                && (board[fromY][fromX] == BlackPawn)
7539                && (board[toY][toX] == EmptySquare)) {
7540         board[fromY][fromX] = EmptySquare;
7541         board[toY][toX] = BlackPawn;
7542         if(oldEP & EP_BEROLIN_A) {
7543                 captured = board[fromY][fromX-1];
7544                 board[fromY][fromX-1] = EmptySquare;
7545         }else{  captured = board[fromY][fromX+1];
7546                 board[fromY][fromX+1] = EmptySquare;
7547         }
7548     } else {
7549         board[toY][toX] = board[fromY][fromX];
7550         board[fromY][fromX] = EmptySquare;
7551     }
7552
7553     /* [HGM] now we promote for Shogi, if needed */
7554     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7555         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7556   }
7557
7558     if (gameInfo.holdingsWidth != 0) {
7559
7560       /* !!A lot more code needs to be written to support holdings  */
7561       /* [HGM] OK, so I have written it. Holdings are stored in the */
7562       /* penultimate board files, so they are automaticlly stored   */
7563       /* in the game history.                                       */
7564       if (fromY == DROP_RANK) {
7565         /* Delete from holdings, by decreasing count */
7566         /* and erasing image if necessary            */
7567         p = (int) fromX;
7568         if(p < (int) BlackPawn) { /* white drop */
7569              p -= (int)WhitePawn;
7570                  p = PieceToNumber((ChessSquare)p);
7571              if(p >= gameInfo.holdingsSize) p = 0;
7572              if(--board[p][BOARD_WIDTH-2] <= 0)
7573                   board[p][BOARD_WIDTH-1] = EmptySquare;
7574              if((int)board[p][BOARD_WIDTH-2] < 0)
7575                         board[p][BOARD_WIDTH-2] = 0;
7576         } else {                  /* black drop */
7577              p -= (int)BlackPawn;
7578                  p = PieceToNumber((ChessSquare)p);
7579              if(p >= gameInfo.holdingsSize) p = 0;
7580              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7581                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7582              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7583                         board[BOARD_HEIGHT-1-p][1] = 0;
7584         }
7585       }
7586       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7587           && gameInfo.variant != VariantBughouse        ) {
7588         /* [HGM] holdings: Add to holdings, if holdings exist */
7589         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7590                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7591                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7592         }
7593         p = (int) captured;
7594         if (p >= (int) BlackPawn) {
7595           p -= (int)BlackPawn;
7596           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7597                   /* in Shogi restore piece to its original  first */
7598                   captured = (ChessSquare) (DEMOTED captured);
7599                   p = DEMOTED p;
7600           }
7601           p = PieceToNumber((ChessSquare)p);
7602           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7603           board[p][BOARD_WIDTH-2]++;
7604           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7605         } else {
7606           p -= (int)WhitePawn;
7607           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7608                   captured = (ChessSquare) (DEMOTED captured);
7609                   p = DEMOTED p;
7610           }
7611           p = PieceToNumber((ChessSquare)p);
7612           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7613           board[BOARD_HEIGHT-1-p][1]++;
7614           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7615         }
7616       }
7617     } else if (gameInfo.variant == VariantAtomic) {
7618       if (captured != EmptySquare) {
7619         int y, x;
7620         for (y = toY-1; y <= toY+1; y++) {
7621           for (x = toX-1; x <= toX+1; x++) {
7622             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7623                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7624               board[y][x] = EmptySquare;
7625             }
7626           }
7627         }
7628         board[toY][toX] = EmptySquare;
7629       }
7630     }
7631     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7632         /* [HGM] Shogi promotions */
7633         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7634     }
7635
7636     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7637                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7638         // [HGM] superchess: take promotion piece out of holdings
7639         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7640         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7641             if(!--board[k][BOARD_WIDTH-2])
7642                 board[k][BOARD_WIDTH-1] = EmptySquare;
7643         } else {
7644             if(!--board[BOARD_HEIGHT-1-k][1])
7645                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7646         }
7647     }
7648
7649 }
7650
7651 /* Updates forwardMostMove */
7652 void
7653 MakeMove(fromX, fromY, toX, toY, promoChar)
7654      int fromX, fromY, toX, toY;
7655      int promoChar;
7656 {
7657 //    forwardMostMove++; // [HGM] bare: moved downstream
7658
7659     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7660         int timeLeft; static int lastLoadFlag=0; int king, piece;
7661         piece = boards[forwardMostMove][fromY][fromX];
7662         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7663         if(gameInfo.variant == VariantKnightmate)
7664             king += (int) WhiteUnicorn - (int) WhiteKing;
7665         if(forwardMostMove == 0) {
7666             if(blackPlaysFirst) 
7667                 fprintf(serverMoves, "%s;", second.tidy);
7668             fprintf(serverMoves, "%s;", first.tidy);
7669             if(!blackPlaysFirst) 
7670                 fprintf(serverMoves, "%s;", second.tidy);
7671         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7672         lastLoadFlag = loadFlag;
7673         // print base move
7674         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7675         // print castling suffix
7676         if( toY == fromY && piece == king ) {
7677             if(toX-fromX > 1)
7678                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7679             if(fromX-toX >1)
7680                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7681         }
7682         // e.p. suffix
7683         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7684              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7685              boards[forwardMostMove][toY][toX] == EmptySquare
7686              && fromX != toX )
7687                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7688         // promotion suffix
7689         if(promoChar != NULLCHAR)
7690                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7691         if(!loadFlag) {
7692             fprintf(serverMoves, "/%d/%d",
7693                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7694             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7695             else                      timeLeft = blackTimeRemaining/1000;
7696             fprintf(serverMoves, "/%d", timeLeft);
7697         }
7698         fflush(serverMoves);
7699     }
7700
7701     if (forwardMostMove+1 >= MAX_MOVES) {
7702       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7703                         0, 1);
7704       return;
7705     }
7706     if (commentList[forwardMostMove+1] != NULL) {
7707         free(commentList[forwardMostMove+1]);
7708         commentList[forwardMostMove+1] = NULL;
7709     }
7710     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7711     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7712     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7713                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7714     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7715     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7716     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7717     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7718     gameInfo.result = GameUnfinished;
7719     if (gameInfo.resultDetails != NULL) {
7720         free(gameInfo.resultDetails);
7721         gameInfo.resultDetails = NULL;
7722     }
7723     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7724                               moveList[forwardMostMove - 1]);
7725     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7726                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7727                              fromY, fromX, toY, toX, promoChar,
7728                              parseList[forwardMostMove - 1]);
7729     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7730                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7731                             castlingRights[forwardMostMove]) ) {
7732       case MT_NONE:
7733       case MT_STALEMATE:
7734       default:
7735         break;
7736       case MT_CHECK:
7737         if(gameInfo.variant != VariantShogi)
7738             strcat(parseList[forwardMostMove - 1], "+");
7739         break;
7740       case MT_CHECKMATE:
7741       case MT_STAINMATE:
7742         strcat(parseList[forwardMostMove - 1], "#");
7743         break;
7744     }
7745     if (appData.debugMode) {
7746         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7747     }
7748
7749 }
7750
7751 /* Updates currentMove if not pausing */
7752 void
7753 ShowMove(fromX, fromY, toX, toY)
7754 {
7755     int instant = (gameMode == PlayFromGameFile) ?
7756         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7757     if(appData.noGUI) return;
7758     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7759         if (!instant) {
7760             if (forwardMostMove == currentMove + 1) {
7761                 AnimateMove(boards[forwardMostMove - 1],
7762                             fromX, fromY, toX, toY);
7763             }
7764             if (appData.highlightLastMove) {
7765                 SetHighlights(fromX, fromY, toX, toY);
7766             }
7767         }
7768         currentMove = forwardMostMove;
7769     }
7770
7771     if (instant) return;
7772
7773     DisplayMove(currentMove - 1);
7774     DrawPosition(FALSE, boards[currentMove]);
7775     DisplayBothClocks();
7776     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7777 }
7778
7779 void SendEgtPath(ChessProgramState *cps)
7780 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7781         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7782
7783         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7784
7785         while(*p) {
7786             char c, *q = name+1, *r, *s;
7787
7788             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7789             while(*p && *p != ',') *q++ = *p++;
7790             *q++ = ':'; *q = 0;
7791             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7792                 strcmp(name, ",nalimov:") == 0 ) {
7793                 // take nalimov path from the menu-changeable option first, if it is defined
7794                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7795                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7796             } else
7797             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7798                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7799                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7800                 s = r = StrStr(s, ":") + 1; // beginning of path info
7801                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7802                 c = *r; *r = 0;             // temporarily null-terminate path info
7803                     *--q = 0;               // strip of trailig ':' from name
7804                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7805                 *r = c;
7806                 SendToProgram(buf,cps);     // send egtbpath command for this format
7807             }
7808             if(*p == ',') p++; // read away comma to position for next format name
7809         }
7810 }
7811
7812 void
7813 InitChessProgram(cps, setup)
7814      ChessProgramState *cps;
7815      int setup; /* [HGM] needed to setup FRC opening position */
7816 {
7817     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7818     if (appData.noChessProgram) return;
7819     hintRequested = FALSE;
7820     bookRequested = FALSE;
7821
7822     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7823     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7824     if(cps->memSize) { /* [HGM] memory */
7825         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7826         SendToProgram(buf, cps);
7827     }
7828     SendEgtPath(cps); /* [HGM] EGT */
7829     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7830         sprintf(buf, "cores %d\n", appData.smpCores);
7831         SendToProgram(buf, cps);
7832     }
7833
7834     SendToProgram(cps->initString, cps);
7835     if (gameInfo.variant != VariantNormal &&
7836         gameInfo.variant != VariantLoadable
7837         /* [HGM] also send variant if board size non-standard */
7838         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7839                                             ) {
7840       char *v = VariantName(gameInfo.variant);
7841       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7842         /* [HGM] in protocol 1 we have to assume all variants valid */
7843         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7844         DisplayFatalError(buf, 0, 1);
7845         return;
7846       }
7847
7848       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7849       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7850       if( gameInfo.variant == VariantXiangqi )
7851            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7852       if( gameInfo.variant == VariantShogi )
7853            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7854       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7855            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7856       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7857                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7858            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7859       if( gameInfo.variant == VariantCourier )
7860            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861       if( gameInfo.variant == VariantSuper )
7862            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7863       if( gameInfo.variant == VariantGreat )
7864            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865
7866       if(overruled) {
7867            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7868                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7869            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7870            if(StrStr(cps->variants, b) == NULL) { 
7871                // specific sized variant not known, check if general sizing allowed
7872                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7873                    if(StrStr(cps->variants, "boardsize") == NULL) {
7874                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7875                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7876                        DisplayFatalError(buf, 0, 1);
7877                        return;
7878                    }
7879                    /* [HGM] here we really should compare with the maximum supported board size */
7880                }
7881            }
7882       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7883       sprintf(buf, "variant %s\n", b);
7884       SendToProgram(buf, cps);
7885     }
7886     currentlyInitializedVariant = gameInfo.variant;
7887
7888     /* [HGM] send opening position in FRC to first engine */
7889     if(setup) {
7890           SendToProgram("force\n", cps);
7891           SendBoard(cps, 0);
7892           /* engine is now in force mode! Set flag to wake it up after first move. */
7893           setboardSpoiledMachineBlack = 1;
7894     }
7895
7896     if (cps->sendICS) {
7897       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7898       SendToProgram(buf, cps);
7899     }
7900     cps->maybeThinking = FALSE;
7901     cps->offeredDraw = 0;
7902     if (!appData.icsActive) {
7903         SendTimeControl(cps, movesPerSession, timeControl,
7904                         timeIncrement, appData.searchDepth,
7905                         searchTime);
7906     }
7907     if (appData.showThinking 
7908         // [HGM] thinking: four options require thinking output to be sent
7909         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7910                                 ) {
7911         SendToProgram("post\n", cps);
7912     }
7913     SendToProgram("hard\n", cps);
7914     if (!appData.ponderNextMove) {
7915         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7916            it without being sure what state we are in first.  "hard"
7917            is not a toggle, so that one is OK.
7918          */
7919         SendToProgram("easy\n", cps);
7920     }
7921     if (cps->usePing) {
7922       sprintf(buf, "ping %d\n", ++cps->lastPing);
7923       SendToProgram(buf, cps);
7924     }
7925     cps->initDone = TRUE;
7926 }   
7927
7928
7929 void
7930 StartChessProgram(cps)
7931      ChessProgramState *cps;
7932 {
7933     char buf[MSG_SIZ];
7934     int err;
7935
7936     if (appData.noChessProgram) return;
7937     cps->initDone = FALSE;
7938
7939     if (strcmp(cps->host, "localhost") == 0) {
7940         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7941     } else if (*appData.remoteShell == NULLCHAR) {
7942         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7943     } else {
7944         if (*appData.remoteUser == NULLCHAR) {
7945           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7946                     cps->program);
7947         } else {
7948           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7949                     cps->host, appData.remoteUser, cps->program);
7950         }
7951         err = StartChildProcess(buf, "", &cps->pr);
7952     }
7953     
7954     if (err != 0) {
7955         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7956         DisplayFatalError(buf, err, 1);
7957         cps->pr = NoProc;
7958         cps->isr = NULL;
7959         return;
7960     }
7961     
7962     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7963     if (cps->protocolVersion > 1) {
7964       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7965       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7966       cps->comboCnt = 0;  //                and values of combo boxes
7967       SendToProgram(buf, cps);
7968     } else {
7969       SendToProgram("xboard\n", cps);
7970     }
7971 }
7972
7973
7974 void
7975 TwoMachinesEventIfReady P((void))
7976 {
7977   if (first.lastPing != first.lastPong) {
7978     DisplayMessage("", _("Waiting for first chess program"));
7979     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7980     return;
7981   }
7982   if (second.lastPing != second.lastPong) {
7983     DisplayMessage("", _("Waiting for second chess program"));
7984     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7985     return;
7986   }
7987   ThawUI();
7988   TwoMachinesEvent();
7989 }
7990
7991 void
7992 NextMatchGame P((void))
7993 {
7994     int index; /* [HGM] autoinc: step load index during match */
7995     Reset(FALSE, TRUE);
7996     if (*appData.loadGameFile != NULLCHAR) {
7997         index = appData.loadGameIndex;
7998         if(index < 0) { // [HGM] autoinc
7999             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8000             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8001         } 
8002         LoadGameFromFile(appData.loadGameFile,
8003                          index,
8004                          appData.loadGameFile, FALSE);
8005     } else if (*appData.loadPositionFile != NULLCHAR) {
8006         index = appData.loadPositionIndex;
8007         if(index < 0) { // [HGM] autoinc
8008             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8009             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8010         } 
8011         LoadPositionFromFile(appData.loadPositionFile,
8012                              index,
8013                              appData.loadPositionFile);
8014     }
8015     TwoMachinesEventIfReady();
8016 }
8017
8018 void UserAdjudicationEvent( int result )
8019 {
8020     ChessMove gameResult = GameIsDrawn;
8021
8022     if( result > 0 ) {
8023         gameResult = WhiteWins;
8024     }
8025     else if( result < 0 ) {
8026         gameResult = BlackWins;
8027     }
8028
8029     if( gameMode == TwoMachinesPlay ) {
8030         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8031     }
8032 }
8033
8034
8035 // [HGM] save: calculate checksum of game to make games easily identifiable
8036 int StringCheckSum(char *s)
8037 {
8038         int i = 0;
8039         if(s==NULL) return 0;
8040         while(*s) i = i*259 + *s++;
8041         return i;
8042 }
8043
8044 int GameCheckSum()
8045 {
8046         int i, sum=0;
8047         for(i=backwardMostMove; i<forwardMostMove; i++) {
8048                 sum += pvInfoList[i].depth;
8049                 sum += StringCheckSum(parseList[i]);
8050                 sum += StringCheckSum(commentList[i]);
8051                 sum *= 261;
8052         }
8053         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8054         return sum + StringCheckSum(commentList[i]);
8055 } // end of save patch
8056
8057 void
8058 GameEnds(result, resultDetails, whosays)
8059      ChessMove result;
8060      char *resultDetails;
8061      int whosays;
8062 {
8063     GameMode nextGameMode;
8064     int isIcsGame;
8065     char buf[MSG_SIZ];
8066
8067     if(endingGame) return; /* [HGM] crash: forbid recursion */
8068     endingGame = 1;
8069
8070     if (appData.debugMode) {
8071       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8072               result, resultDetails ? resultDetails : "(null)", whosays);
8073     }
8074
8075     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8076         /* If we are playing on ICS, the server decides when the
8077            game is over, but the engine can offer to draw, claim 
8078            a draw, or resign. 
8079          */
8080 #if ZIPPY
8081         if (appData.zippyPlay && first.initDone) {
8082             if (result == GameIsDrawn) {
8083                 /* In case draw still needs to be claimed */
8084                 SendToICS(ics_prefix);
8085                 SendToICS("draw\n");
8086             } else if (StrCaseStr(resultDetails, "resign")) {
8087                 SendToICS(ics_prefix);
8088                 SendToICS("resign\n");
8089             }
8090         }
8091 #endif
8092         endingGame = 0; /* [HGM] crash */
8093         return;
8094     }
8095
8096     /* If we're loading the game from a file, stop */
8097     if (whosays == GE_FILE) {
8098       (void) StopLoadGameTimer();
8099       gameFileFP = NULL;
8100     }
8101
8102     /* Cancel draw offers */
8103     first.offeredDraw = second.offeredDraw = 0;
8104
8105     /* If this is an ICS game, only ICS can really say it's done;
8106        if not, anyone can. */
8107     isIcsGame = (gameMode == IcsPlayingWhite || 
8108                  gameMode == IcsPlayingBlack || 
8109                  gameMode == IcsObserving    || 
8110                  gameMode == IcsExamining);
8111
8112     if (!isIcsGame || whosays == GE_ICS) {
8113         /* OK -- not an ICS game, or ICS said it was done */
8114         StopClocks();
8115         if (!isIcsGame && !appData.noChessProgram) 
8116           SetUserThinkingEnables();
8117     
8118         /* [HGM] if a machine claims the game end we verify this claim */
8119         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8120             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8121                 char claimer;
8122                 ChessMove trueResult = (ChessMove) -1;
8123
8124                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8125                                             first.twoMachinesColor[0] :
8126                                             second.twoMachinesColor[0] ;
8127
8128                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8129                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8130                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8131                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8132                 } else
8133                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8134                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8135                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8136                 } else
8137                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8138                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8139                 }
8140
8141                 // now verify win claims, but not in drop games, as we don't understand those yet
8142                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8143                                                  || gameInfo.variant == VariantGreat) &&
8144                     (result == WhiteWins && claimer == 'w' ||
8145                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8146                       if (appData.debugMode) {
8147                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8148                                 result, epStatus[forwardMostMove], forwardMostMove);
8149                       }
8150                       if(result != trueResult) {
8151                               sprintf(buf, "False win claim: '%s'", resultDetails);
8152                               result = claimer == 'w' ? BlackWins : WhiteWins;
8153                               resultDetails = buf;
8154                       }
8155                 } else
8156                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8157                     && (forwardMostMove <= backwardMostMove ||
8158                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8159                         (claimer=='b')==(forwardMostMove&1))
8160                                                                                   ) {
8161                       /* [HGM] verify: draws that were not flagged are false claims */
8162                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8163                       result = claimer == 'w' ? BlackWins : WhiteWins;
8164                       resultDetails = buf;
8165                 }
8166                 /* (Claiming a loss is accepted no questions asked!) */
8167             }
8168             /* [HGM] bare: don't allow bare King to win */
8169             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8170                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8171                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8172                && result != GameIsDrawn)
8173             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8174                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8175                         int p = (int)boards[forwardMostMove][i][j] - color;
8176                         if(p >= 0 && p <= (int)WhiteKing) k++;
8177                 }
8178                 if (appData.debugMode) {
8179                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8180                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8181                 }
8182                 if(k <= 1) {
8183                         result = GameIsDrawn;
8184                         sprintf(buf, "%s but bare king", resultDetails);
8185                         resultDetails = buf;
8186                 }
8187             }
8188         }
8189
8190
8191         if(serverMoves != NULL && !loadFlag) { char c = '=';
8192             if(result==WhiteWins) c = '+';
8193             if(result==BlackWins) c = '-';
8194             if(resultDetails != NULL)
8195                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8196         }
8197         if (resultDetails != NULL) {
8198             gameInfo.result = result;
8199             gameInfo.resultDetails = StrSave(resultDetails);
8200
8201             /* display last move only if game was not loaded from file */
8202             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8203                 DisplayMove(currentMove - 1);
8204     
8205             if (forwardMostMove != 0) {
8206                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8207                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8208                                                                 ) {
8209                     if (*appData.saveGameFile != NULLCHAR) {
8210                         SaveGameToFile(appData.saveGameFile, TRUE);
8211                     } else if (appData.autoSaveGames) {
8212                         AutoSaveGame();
8213                     }
8214                     if (*appData.savePositionFile != NULLCHAR) {
8215                         SavePositionToFile(appData.savePositionFile);
8216                     }
8217                 }
8218             }
8219
8220             /* Tell program how game ended in case it is learning */
8221             /* [HGM] Moved this to after saving the PGN, just in case */
8222             /* engine died and we got here through time loss. In that */
8223             /* case we will get a fatal error writing the pipe, which */
8224             /* would otherwise lose us the PGN.                       */
8225             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8226             /* output during GameEnds should never be fatal anymore   */
8227             if (gameMode == MachinePlaysWhite ||
8228                 gameMode == MachinePlaysBlack ||
8229                 gameMode == TwoMachinesPlay ||
8230                 gameMode == IcsPlayingWhite ||
8231                 gameMode == IcsPlayingBlack ||
8232                 gameMode == BeginningOfGame) {
8233                 char buf[MSG_SIZ];
8234                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8235                         resultDetails);
8236                 if (first.pr != NoProc) {
8237                     SendToProgram(buf, &first);
8238                 }
8239                 if (second.pr != NoProc &&
8240                     gameMode == TwoMachinesPlay) {
8241                     SendToProgram(buf, &second);
8242                 }
8243             }
8244         }
8245
8246         if (appData.icsActive) {
8247             if (appData.quietPlay &&
8248                 (gameMode == IcsPlayingWhite ||
8249                  gameMode == IcsPlayingBlack)) {
8250                 SendToICS(ics_prefix);
8251                 SendToICS("set shout 1\n");
8252             }
8253             nextGameMode = IcsIdle;
8254             ics_user_moved = FALSE;
8255             /* clean up premove.  It's ugly when the game has ended and the
8256              * premove highlights are still on the board.
8257              */
8258             if (gotPremove) {
8259               gotPremove = FALSE;
8260               ClearPremoveHighlights();
8261               DrawPosition(FALSE, boards[currentMove]);
8262             }
8263             if (whosays == GE_ICS) {
8264                 switch (result) {
8265                 case WhiteWins:
8266                     if (gameMode == IcsPlayingWhite)
8267                         PlayIcsWinSound();
8268                     else if(gameMode == IcsPlayingBlack)
8269                         PlayIcsLossSound();
8270                     break;
8271                 case BlackWins:
8272                     if (gameMode == IcsPlayingBlack)
8273                         PlayIcsWinSound();
8274                     else if(gameMode == IcsPlayingWhite)
8275                         PlayIcsLossSound();
8276                     break;
8277                 case GameIsDrawn:
8278                     PlayIcsDrawSound();
8279                     break;
8280                 default:
8281                     PlayIcsUnfinishedSound();
8282                 }
8283             }
8284         } else if (gameMode == EditGame ||
8285                    gameMode == PlayFromGameFile || 
8286                    gameMode == AnalyzeMode || 
8287                    gameMode == AnalyzeFile) {
8288             nextGameMode = gameMode;
8289         } else {
8290             nextGameMode = EndOfGame;
8291         }
8292         pausing = FALSE;
8293         ModeHighlight();
8294     } else {
8295         nextGameMode = gameMode;
8296     }
8297
8298     if (appData.noChessProgram) {
8299         gameMode = nextGameMode;
8300         ModeHighlight();
8301         endingGame = 0; /* [HGM] crash */
8302         return;
8303     }
8304
8305     if (first.reuse) {
8306         /* Put first chess program into idle state */
8307         if (first.pr != NoProc &&
8308             (gameMode == MachinePlaysWhite ||
8309              gameMode == MachinePlaysBlack ||
8310              gameMode == TwoMachinesPlay ||
8311              gameMode == IcsPlayingWhite ||
8312              gameMode == IcsPlayingBlack ||
8313              gameMode == BeginningOfGame)) {
8314             SendToProgram("force\n", &first);
8315             if (first.usePing) {
8316               char buf[MSG_SIZ];
8317               sprintf(buf, "ping %d\n", ++first.lastPing);
8318               SendToProgram(buf, &first);
8319             }
8320         }
8321     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8322         /* Kill off first chess program */
8323         if (first.isr != NULL)
8324           RemoveInputSource(first.isr);
8325         first.isr = NULL;
8326     
8327         if (first.pr != NoProc) {
8328             ExitAnalyzeMode();
8329             DoSleep( appData.delayBeforeQuit );
8330             SendToProgram("quit\n", &first);
8331             DoSleep( appData.delayAfterQuit );
8332             DestroyChildProcess(first.pr, first.useSigterm);
8333         }
8334         first.pr = NoProc;
8335     }
8336     if (second.reuse) {
8337         /* Put second chess program into idle state */
8338         if (second.pr != NoProc &&
8339             gameMode == TwoMachinesPlay) {
8340             SendToProgram("force\n", &second);
8341             if (second.usePing) {
8342               char buf[MSG_SIZ];
8343               sprintf(buf, "ping %d\n", ++second.lastPing);
8344               SendToProgram(buf, &second);
8345             }
8346         }
8347     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8348         /* Kill off second chess program */
8349         if (second.isr != NULL)
8350           RemoveInputSource(second.isr);
8351         second.isr = NULL;
8352     
8353         if (second.pr != NoProc) {
8354             DoSleep( appData.delayBeforeQuit );
8355             SendToProgram("quit\n", &second);
8356             DoSleep( appData.delayAfterQuit );
8357             DestroyChildProcess(second.pr, second.useSigterm);
8358         }
8359         second.pr = NoProc;
8360     }
8361
8362     if (matchMode && gameMode == TwoMachinesPlay) {
8363         switch (result) {
8364         case WhiteWins:
8365           if (first.twoMachinesColor[0] == 'w') {
8366             first.matchWins++;
8367           } else {
8368             second.matchWins++;
8369           }
8370           break;
8371         case BlackWins:
8372           if (first.twoMachinesColor[0] == 'b') {
8373             first.matchWins++;
8374           } else {
8375             second.matchWins++;
8376           }
8377           break;
8378         default:
8379           break;
8380         }
8381         if (matchGame < appData.matchGames) {
8382             char *tmp;
8383             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8384                 tmp = first.twoMachinesColor;
8385                 first.twoMachinesColor = second.twoMachinesColor;
8386                 second.twoMachinesColor = tmp;
8387             }
8388             gameMode = nextGameMode;
8389             matchGame++;
8390             if(appData.matchPause>10000 || appData.matchPause<10)
8391                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8392             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8393             endingGame = 0; /* [HGM] crash */
8394             return;
8395         } else {
8396             char buf[MSG_SIZ];
8397             gameMode = nextGameMode;
8398             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8399                     first.tidy, second.tidy,
8400                     first.matchWins, second.matchWins,
8401                     appData.matchGames - (first.matchWins + second.matchWins));
8402             DisplayFatalError(buf, 0, 0);
8403         }
8404     }
8405     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8406         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8407       ExitAnalyzeMode();
8408     gameMode = nextGameMode;
8409     ModeHighlight();
8410     endingGame = 0;  /* [HGM] crash */
8411 }
8412
8413 /* Assumes program was just initialized (initString sent).
8414    Leaves program in force mode. */
8415 void
8416 FeedMovesToProgram(cps, upto) 
8417      ChessProgramState *cps;
8418      int upto;
8419 {
8420     int i;
8421     
8422     if (appData.debugMode)
8423       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8424               startedFromSetupPosition ? "position and " : "",
8425               backwardMostMove, upto, cps->which);
8426     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8427         // [HGM] variantswitch: make engine aware of new variant
8428         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8429                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8430         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8431         SendToProgram(buf, cps);
8432         currentlyInitializedVariant = gameInfo.variant;
8433     }
8434     SendToProgram("force\n", cps);
8435     if (startedFromSetupPosition) {
8436         SendBoard(cps, backwardMostMove);
8437     if (appData.debugMode) {
8438         fprintf(debugFP, "feedMoves\n");
8439     }
8440     }
8441     for (i = backwardMostMove; i < upto; i++) {
8442         SendMoveToProgram(i, cps);
8443     }
8444 }
8445
8446
8447 void
8448 ResurrectChessProgram()
8449 {
8450      /* The chess program may have exited.
8451         If so, restart it and feed it all the moves made so far. */
8452
8453     if (appData.noChessProgram || first.pr != NoProc) return;
8454     
8455     StartChessProgram(&first);
8456     InitChessProgram(&first, FALSE);
8457     FeedMovesToProgram(&first, currentMove);
8458
8459     if (!first.sendTime) {
8460         /* can't tell gnuchess what its clock should read,
8461            so we bow to its notion. */
8462         ResetClocks();
8463         timeRemaining[0][currentMove] = whiteTimeRemaining;
8464         timeRemaining[1][currentMove] = blackTimeRemaining;
8465     }
8466
8467     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8468                 appData.icsEngineAnalyze) && first.analysisSupport) {
8469       SendToProgram("analyze\n", &first);
8470       first.analyzing = TRUE;
8471     }
8472 }
8473
8474 /*
8475  * Button procedures
8476  */
8477 void
8478 Reset(redraw, init)
8479      int redraw, init;
8480 {
8481     int i;
8482
8483     if (appData.debugMode) {
8484         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8485                 redraw, init, gameMode);
8486     }
8487     pausing = pauseExamInvalid = FALSE;
8488     startedFromSetupPosition = blackPlaysFirst = FALSE;
8489     firstMove = TRUE;
8490     whiteFlag = blackFlag = FALSE;
8491     userOfferedDraw = FALSE;
8492     hintRequested = bookRequested = FALSE;
8493     first.maybeThinking = FALSE;
8494     second.maybeThinking = FALSE;
8495     first.bookSuspend = FALSE; // [HGM] book
8496     second.bookSuspend = FALSE;
8497     thinkOutput[0] = NULLCHAR;
8498     lastHint[0] = NULLCHAR;
8499     ClearGameInfo(&gameInfo);
8500     gameInfo.variant = StringToVariant(appData.variant);
8501     ics_user_moved = ics_clock_paused = FALSE;
8502     ics_getting_history = H_FALSE;
8503     ics_gamenum = -1;
8504     white_holding[0] = black_holding[0] = NULLCHAR;
8505     ClearProgramStats();
8506     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8507     
8508     ResetFrontEnd();
8509     ClearHighlights();
8510     flipView = appData.flipView;
8511     ClearPremoveHighlights();
8512     gotPremove = FALSE;
8513     alarmSounded = FALSE;
8514
8515     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8516     if(appData.serverMovesName != NULL) {
8517         /* [HGM] prepare to make moves file for broadcasting */
8518         clock_t t = clock();
8519         if(serverMoves != NULL) fclose(serverMoves);
8520         serverMoves = fopen(appData.serverMovesName, "r");
8521         if(serverMoves != NULL) {
8522             fclose(serverMoves);
8523             /* delay 15 sec before overwriting, so all clients can see end */
8524             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8525         }
8526         serverMoves = fopen(appData.serverMovesName, "w");
8527     }
8528
8529     ExitAnalyzeMode();
8530     gameMode = BeginningOfGame;
8531     ModeHighlight();
8532     if(appData.icsActive) gameInfo.variant = VariantNormal;
8533     currentMove = forwardMostMove = backwardMostMove = 0;
8534     InitPosition(redraw);
8535     for (i = 0; i < MAX_MOVES; i++) {
8536         if (commentList[i] != NULL) {
8537             free(commentList[i]);
8538             commentList[i] = NULL;
8539         }
8540     }
8541     ResetClocks();
8542     timeRemaining[0][0] = whiteTimeRemaining;
8543     timeRemaining[1][0] = blackTimeRemaining;
8544     if (first.pr == NULL) {
8545         StartChessProgram(&first);
8546     }
8547     if (init) {
8548             InitChessProgram(&first, startedFromSetupPosition);
8549     }
8550     DisplayTitle("");
8551     DisplayMessage("", "");
8552     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8553     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8554 }
8555
8556 void
8557 AutoPlayGameLoop()
8558 {
8559     for (;;) {
8560         if (!AutoPlayOneMove())
8561           return;
8562         if (matchMode || appData.timeDelay == 0)
8563           continue;
8564         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8565           return;
8566         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8567         break;
8568     }
8569 }
8570
8571
8572 int
8573 AutoPlayOneMove()
8574 {
8575     int fromX, fromY, toX, toY;
8576
8577     if (appData.debugMode) {
8578       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8579     }
8580
8581     if (gameMode != PlayFromGameFile)
8582       return FALSE;
8583
8584     if (currentMove >= forwardMostMove) {
8585       gameMode = EditGame;
8586       ModeHighlight();
8587
8588       /* [AS] Clear current move marker at the end of a game */
8589       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8590
8591       return FALSE;
8592     }
8593     
8594     toX = moveList[currentMove][2] - AAA;
8595     toY = moveList[currentMove][3] - ONE;
8596
8597     if (moveList[currentMove][1] == '@') {
8598         if (appData.highlightLastMove) {
8599             SetHighlights(-1, -1, toX, toY);
8600         }
8601     } else {
8602         fromX = moveList[currentMove][0] - AAA;
8603         fromY = moveList[currentMove][1] - ONE;
8604
8605         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8606
8607         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8608
8609         if (appData.highlightLastMove) {
8610             SetHighlights(fromX, fromY, toX, toY);
8611         }
8612     }
8613     DisplayMove(currentMove);
8614     SendMoveToProgram(currentMove++, &first);
8615     DisplayBothClocks();
8616     DrawPosition(FALSE, boards[currentMove]);
8617     // [HGM] PV info: always display, routine tests if empty
8618     DisplayComment(currentMove - 1, commentList[currentMove]);
8619     return TRUE;
8620 }
8621
8622
8623 int
8624 LoadGameOneMove(readAhead)
8625      ChessMove readAhead;
8626 {
8627     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8628     char promoChar = NULLCHAR;
8629     ChessMove moveType;
8630     char move[MSG_SIZ];
8631     char *p, *q;
8632     
8633     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8634         gameMode != AnalyzeMode && gameMode != Training) {
8635         gameFileFP = NULL;
8636         return FALSE;
8637     }
8638     
8639     yyboardindex = forwardMostMove;
8640     if (readAhead != (ChessMove)0) {
8641       moveType = readAhead;
8642     } else {
8643       if (gameFileFP == NULL)
8644           return FALSE;
8645       moveType = (ChessMove) yylex();
8646     }
8647     
8648     done = FALSE;
8649     switch (moveType) {
8650       case Comment:
8651         if (appData.debugMode) 
8652           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8653         p = yy_text;
8654         if (*p == '{' || *p == '[' || *p == '(') {
8655             p[strlen(p) - 1] = NULLCHAR;
8656             p++;
8657         }
8658
8659         /* append the comment but don't display it */
8660         while (*p == '\n') p++;
8661         AppendComment(currentMove, p);
8662         return TRUE;
8663
8664       case WhiteCapturesEnPassant:
8665       case BlackCapturesEnPassant:
8666       case WhitePromotionChancellor:
8667       case BlackPromotionChancellor:
8668       case WhitePromotionArchbishop:
8669       case BlackPromotionArchbishop:
8670       case WhitePromotionCentaur:
8671       case BlackPromotionCentaur:
8672       case WhitePromotionQueen:
8673       case BlackPromotionQueen:
8674       case WhitePromotionRook:
8675       case BlackPromotionRook:
8676       case WhitePromotionBishop:
8677       case BlackPromotionBishop:
8678       case WhitePromotionKnight:
8679       case BlackPromotionKnight:
8680       case WhitePromotionKing:
8681       case BlackPromotionKing:
8682       case NormalMove:
8683       case WhiteKingSideCastle:
8684       case WhiteQueenSideCastle:
8685       case BlackKingSideCastle:
8686       case BlackQueenSideCastle:
8687       case WhiteKingSideCastleWild:
8688       case WhiteQueenSideCastleWild:
8689       case BlackKingSideCastleWild:
8690       case BlackQueenSideCastleWild:
8691       /* PUSH Fabien */
8692       case WhiteHSideCastleFR:
8693       case WhiteASideCastleFR:
8694       case BlackHSideCastleFR:
8695       case BlackASideCastleFR:
8696       /* POP Fabien */
8697         if (appData.debugMode)
8698           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8699         fromX = currentMoveString[0] - AAA;
8700         fromY = currentMoveString[1] - ONE;
8701         toX = currentMoveString[2] - AAA;
8702         toY = currentMoveString[3] - ONE;
8703         promoChar = currentMoveString[4];
8704         break;
8705
8706       case WhiteDrop:
8707       case BlackDrop:
8708         if (appData.debugMode)
8709           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8710         fromX = moveType == WhiteDrop ?
8711           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8712         (int) CharToPiece(ToLower(currentMoveString[0]));
8713         fromY = DROP_RANK;
8714         toX = currentMoveString[2] - AAA;
8715         toY = currentMoveString[3] - ONE;
8716         break;
8717
8718       case WhiteWins:
8719       case BlackWins:
8720       case GameIsDrawn:
8721       case GameUnfinished:
8722         if (appData.debugMode)
8723           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8724         p = strchr(yy_text, '{');
8725         if (p == NULL) p = strchr(yy_text, '(');
8726         if (p == NULL) {
8727             p = yy_text;
8728             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8729         } else {
8730             q = strchr(p, *p == '{' ? '}' : ')');
8731             if (q != NULL) *q = NULLCHAR;
8732             p++;
8733         }
8734         GameEnds(moveType, p, GE_FILE);
8735         done = TRUE;
8736         if (cmailMsgLoaded) {
8737             ClearHighlights();
8738             flipView = WhiteOnMove(currentMove);
8739             if (moveType == GameUnfinished) flipView = !flipView;
8740             if (appData.debugMode)
8741               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8742         }
8743         break;
8744
8745       case (ChessMove) 0:       /* end of file */
8746         if (appData.debugMode)
8747           fprintf(debugFP, "Parser hit end of file\n");
8748         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8749                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8750           case MT_NONE:
8751           case MT_CHECK:
8752             break;
8753           case MT_CHECKMATE:
8754           case MT_STAINMATE:
8755             if (WhiteOnMove(currentMove)) {
8756                 GameEnds(BlackWins, "Black mates", GE_FILE);
8757             } else {
8758                 GameEnds(WhiteWins, "White mates", GE_FILE);
8759             }
8760             break;
8761           case MT_STALEMATE:
8762             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8763             break;
8764         }
8765         done = TRUE;
8766         break;
8767
8768       case MoveNumberOne:
8769         if (lastLoadGameStart == GNUChessGame) {
8770             /* GNUChessGames have numbers, but they aren't move numbers */
8771             if (appData.debugMode)
8772               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8773                       yy_text, (int) moveType);
8774             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8775         }
8776         /* else fall thru */
8777
8778       case XBoardGame:
8779       case GNUChessGame:
8780       case PGNTag:
8781         /* Reached start of next game in file */
8782         if (appData.debugMode)
8783           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8784         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8785                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8786           case MT_NONE:
8787           case MT_CHECK:
8788             break;
8789           case MT_CHECKMATE:
8790           case MT_STAINMATE:
8791             if (WhiteOnMove(currentMove)) {
8792                 GameEnds(BlackWins, "Black mates", GE_FILE);
8793             } else {
8794                 GameEnds(WhiteWins, "White mates", GE_FILE);
8795             }
8796             break;
8797           case MT_STALEMATE:
8798             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8799             break;
8800         }
8801         done = TRUE;
8802         break;
8803
8804       case PositionDiagram:     /* should not happen; ignore */
8805       case ElapsedTime:         /* ignore */
8806       case NAG:                 /* ignore */
8807         if (appData.debugMode)
8808           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8809                   yy_text, (int) moveType);
8810         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8811
8812       case IllegalMove:
8813         if (appData.testLegality) {
8814             if (appData.debugMode)
8815               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8816             sprintf(move, _("Illegal move: %d.%s%s"),
8817                     (forwardMostMove / 2) + 1,
8818                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8819             DisplayError(move, 0);
8820             done = TRUE;
8821         } else {
8822             if (appData.debugMode)
8823               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8824                       yy_text, currentMoveString);
8825             fromX = currentMoveString[0] - AAA;
8826             fromY = currentMoveString[1] - ONE;
8827             toX = currentMoveString[2] - AAA;
8828             toY = currentMoveString[3] - ONE;
8829             promoChar = currentMoveString[4];
8830         }
8831         break;
8832
8833       case AmbiguousMove:
8834         if (appData.debugMode)
8835           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8836         sprintf(move, _("Ambiguous move: %d.%s%s"),
8837                 (forwardMostMove / 2) + 1,
8838                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8839         DisplayError(move, 0);
8840         done = TRUE;
8841         break;
8842
8843       default:
8844       case ImpossibleMove:
8845         if (appData.debugMode)
8846           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8847         sprintf(move, _("Illegal move: %d.%s%s"),
8848                 (forwardMostMove / 2) + 1,
8849                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8850         DisplayError(move, 0);
8851         done = TRUE;
8852         break;
8853     }
8854
8855     if (done) {
8856         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8857             DrawPosition(FALSE, boards[currentMove]);
8858             DisplayBothClocks();
8859             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8860               DisplayComment(currentMove - 1, commentList[currentMove]);
8861         }
8862         (void) StopLoadGameTimer();
8863         gameFileFP = NULL;
8864         cmailOldMove = forwardMostMove;
8865         return FALSE;
8866     } else {
8867         /* currentMoveString is set as a side-effect of yylex */
8868         strcat(currentMoveString, "\n");
8869         strcpy(moveList[forwardMostMove], currentMoveString);
8870         
8871         thinkOutput[0] = NULLCHAR;
8872         MakeMove(fromX, fromY, toX, toY, promoChar);
8873         currentMove = forwardMostMove;
8874         return TRUE;
8875     }
8876 }
8877
8878 /* Load the nth game from the given file */
8879 int
8880 LoadGameFromFile(filename, n, title, useList)
8881      char *filename;
8882      int n;
8883      char *title;
8884      /*Boolean*/ int useList;
8885 {
8886     FILE *f;
8887     char buf[MSG_SIZ];
8888
8889     if (strcmp(filename, "-") == 0) {
8890         f = stdin;
8891         title = "stdin";
8892     } else {
8893         f = fopen(filename, "rb");
8894         if (f == NULL) {
8895           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8896             DisplayError(buf, errno);
8897             return FALSE;
8898         }
8899     }
8900     if (fseek(f, 0, 0) == -1) {
8901         /* f is not seekable; probably a pipe */
8902         useList = FALSE;
8903     }
8904     if (useList && n == 0) {
8905         int error = GameListBuild(f);
8906         if (error) {
8907             DisplayError(_("Cannot build game list"), error);
8908         } else if (!ListEmpty(&gameList) &&
8909                    ((ListGame *) gameList.tailPred)->number > 1) {
8910             GameListPopUp(f, title);
8911             return TRUE;
8912         }
8913         GameListDestroy();
8914         n = 1;
8915     }
8916     if (n == 0) n = 1;
8917     return LoadGame(f, n, title, FALSE);
8918 }
8919
8920
8921 void
8922 MakeRegisteredMove()
8923 {
8924     int fromX, fromY, toX, toY;
8925     char promoChar;
8926     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8927         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8928           case CMAIL_MOVE:
8929           case CMAIL_DRAW:
8930             if (appData.debugMode)
8931               fprintf(debugFP, "Restoring %s for game %d\n",
8932                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8933     
8934             thinkOutput[0] = NULLCHAR;
8935             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8936             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8937             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8938             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8939             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8940             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8941             MakeMove(fromX, fromY, toX, toY, promoChar);
8942             ShowMove(fromX, fromY, toX, toY);
8943               
8944             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8945                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8946               case MT_NONE:
8947               case MT_CHECK:
8948                 break;
8949                 
8950               case MT_CHECKMATE:
8951               case MT_STAINMATE:
8952                 if (WhiteOnMove(currentMove)) {
8953                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8954                 } else {
8955                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8956                 }
8957                 break;
8958                 
8959               case MT_STALEMATE:
8960                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8961                 break;
8962             }
8963
8964             break;
8965             
8966           case CMAIL_RESIGN:
8967             if (WhiteOnMove(currentMove)) {
8968                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8969             } else {
8970                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8971             }
8972             break;
8973             
8974           case CMAIL_ACCEPT:
8975             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8976             break;
8977               
8978           default:
8979             break;
8980         }
8981     }
8982
8983     return;
8984 }
8985
8986 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8987 int
8988 CmailLoadGame(f, gameNumber, title, useList)
8989      FILE *f;
8990      int gameNumber;
8991      char *title;
8992      int useList;
8993 {
8994     int retVal;
8995
8996     if (gameNumber > nCmailGames) {
8997         DisplayError(_("No more games in this message"), 0);
8998         return FALSE;
8999     }
9000     if (f == lastLoadGameFP) {
9001         int offset = gameNumber - lastLoadGameNumber;
9002         if (offset == 0) {
9003             cmailMsg[0] = NULLCHAR;
9004             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9005                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9006                 nCmailMovesRegistered--;
9007             }
9008             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9009             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9010                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9011             }
9012         } else {
9013             if (! RegisterMove()) return FALSE;
9014         }
9015     }
9016
9017     retVal = LoadGame(f, gameNumber, title, useList);
9018
9019     /* Make move registered during previous look at this game, if any */
9020     MakeRegisteredMove();
9021
9022     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9023         commentList[currentMove]
9024           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9025         DisplayComment(currentMove - 1, commentList[currentMove]);
9026     }
9027
9028     return retVal;
9029 }
9030
9031 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9032 int
9033 ReloadGame(offset)
9034      int offset;
9035 {
9036     int gameNumber = lastLoadGameNumber + offset;
9037     if (lastLoadGameFP == NULL) {
9038         DisplayError(_("No game has been loaded yet"), 0);
9039         return FALSE;
9040     }
9041     if (gameNumber <= 0) {
9042         DisplayError(_("Can't back up any further"), 0);
9043         return FALSE;
9044     }
9045     if (cmailMsgLoaded) {
9046         return CmailLoadGame(lastLoadGameFP, gameNumber,
9047                              lastLoadGameTitle, lastLoadGameUseList);
9048     } else {
9049         return LoadGame(lastLoadGameFP, gameNumber,
9050                         lastLoadGameTitle, lastLoadGameUseList);
9051     }
9052 }
9053
9054
9055
9056 /* Load the nth game from open file f */
9057 int
9058 LoadGame(f, gameNumber, title, useList)
9059      FILE *f;
9060      int gameNumber;
9061      char *title;
9062      int useList;
9063 {
9064     ChessMove cm;
9065     char buf[MSG_SIZ];
9066     int gn = gameNumber;
9067     ListGame *lg = NULL;
9068     int numPGNTags = 0;
9069     int err;
9070     GameMode oldGameMode;
9071     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9072
9073     if (appData.debugMode) 
9074         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9075
9076     if (gameMode == Training )
9077         SetTrainingModeOff();
9078
9079     oldGameMode = gameMode;
9080     if (gameMode != BeginningOfGame) {
9081       Reset(FALSE, TRUE);
9082     }
9083
9084     gameFileFP = f;
9085     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9086         fclose(lastLoadGameFP);
9087     }
9088
9089     if (useList) {
9090         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9091         
9092         if (lg) {
9093             fseek(f, lg->offset, 0);
9094             GameListHighlight(gameNumber);
9095             gn = 1;
9096         }
9097         else {
9098             DisplayError(_("Game number out of range"), 0);
9099             return FALSE;
9100         }
9101     } else {
9102         GameListDestroy();
9103         if (fseek(f, 0, 0) == -1) {
9104             if (f == lastLoadGameFP ?
9105                 gameNumber == lastLoadGameNumber + 1 :
9106                 gameNumber == 1) {
9107                 gn = 1;
9108             } else {
9109                 DisplayError(_("Can't seek on game file"), 0);
9110                 return FALSE;
9111             }
9112         }
9113     }
9114     lastLoadGameFP = f;
9115     lastLoadGameNumber = gameNumber;
9116     strcpy(lastLoadGameTitle, title);
9117     lastLoadGameUseList = useList;
9118
9119     yynewfile(f);
9120
9121     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9122       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9123                 lg->gameInfo.black);
9124             DisplayTitle(buf);
9125     } else if (*title != NULLCHAR) {
9126         if (gameNumber > 1) {
9127             sprintf(buf, "%s %d", title, gameNumber);
9128             DisplayTitle(buf);
9129         } else {
9130             DisplayTitle(title);
9131         }
9132     }
9133
9134     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9135         gameMode = PlayFromGameFile;
9136         ModeHighlight();
9137     }
9138
9139     currentMove = forwardMostMove = backwardMostMove = 0;
9140     CopyBoard(boards[0], initialPosition);
9141     StopClocks();
9142
9143     /*
9144      * Skip the first gn-1 games in the file.
9145      * Also skip over anything that precedes an identifiable 
9146      * start of game marker, to avoid being confused by 
9147      * garbage at the start of the file.  Currently 
9148      * recognized start of game markers are the move number "1",
9149      * the pattern "gnuchess .* game", the pattern
9150      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9151      * A game that starts with one of the latter two patterns
9152      * will also have a move number 1, possibly
9153      * following a position diagram.
9154      * 5-4-02: Let's try being more lenient and allowing a game to
9155      * start with an unnumbered move.  Does that break anything?
9156      */
9157     cm = lastLoadGameStart = (ChessMove) 0;
9158     while (gn > 0) {
9159         yyboardindex = forwardMostMove;
9160         cm = (ChessMove) yylex();
9161         switch (cm) {
9162           case (ChessMove) 0:
9163             if (cmailMsgLoaded) {
9164                 nCmailGames = CMAIL_MAX_GAMES - gn;
9165             } else {
9166                 Reset(TRUE, TRUE);
9167                 DisplayError(_("Game not found in file"), 0);
9168             }
9169             return FALSE;
9170
9171           case GNUChessGame:
9172           case XBoardGame:
9173             gn--;
9174             lastLoadGameStart = cm;
9175             break;
9176             
9177           case MoveNumberOne:
9178             switch (lastLoadGameStart) {
9179               case GNUChessGame:
9180               case XBoardGame:
9181               case PGNTag:
9182                 break;
9183               case MoveNumberOne:
9184               case (ChessMove) 0:
9185                 gn--;           /* count this game */
9186                 lastLoadGameStart = cm;
9187                 break;
9188               default:
9189                 /* impossible */
9190                 break;
9191             }
9192             break;
9193
9194           case PGNTag:
9195             switch (lastLoadGameStart) {
9196               case GNUChessGame:
9197               case PGNTag:
9198               case MoveNumberOne:
9199               case (ChessMove) 0:
9200                 gn--;           /* count this game */
9201                 lastLoadGameStart = cm;
9202                 break;
9203               case XBoardGame:
9204                 lastLoadGameStart = cm; /* game counted already */
9205                 break;
9206               default:
9207                 /* impossible */
9208                 break;
9209             }
9210             if (gn > 0) {
9211                 do {
9212                     yyboardindex = forwardMostMove;
9213                     cm = (ChessMove) yylex();
9214                 } while (cm == PGNTag || cm == Comment);
9215             }
9216             break;
9217
9218           case WhiteWins:
9219           case BlackWins:
9220           case GameIsDrawn:
9221             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9222                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9223                     != CMAIL_OLD_RESULT) {
9224                     nCmailResults ++ ;
9225                     cmailResult[  CMAIL_MAX_GAMES
9226                                 - gn - 1] = CMAIL_OLD_RESULT;
9227                 }
9228             }
9229             break;
9230
9231           case NormalMove:
9232             /* Only a NormalMove can be at the start of a game
9233              * without a position diagram. */
9234             if (lastLoadGameStart == (ChessMove) 0) {
9235               gn--;
9236               lastLoadGameStart = MoveNumberOne;
9237             }
9238             break;
9239
9240           default:
9241             break;
9242         }
9243     }
9244     
9245     if (appData.debugMode)
9246       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9247
9248     if (cm == XBoardGame) {
9249         /* Skip any header junk before position diagram and/or move 1 */
9250         for (;;) {
9251             yyboardindex = forwardMostMove;
9252             cm = (ChessMove) yylex();
9253
9254             if (cm == (ChessMove) 0 ||
9255                 cm == GNUChessGame || cm == XBoardGame) {
9256                 /* Empty game; pretend end-of-file and handle later */
9257                 cm = (ChessMove) 0;
9258                 break;
9259             }
9260
9261             if (cm == MoveNumberOne || cm == PositionDiagram ||
9262                 cm == PGNTag || cm == Comment)
9263               break;
9264         }
9265     } else if (cm == GNUChessGame) {
9266         if (gameInfo.event != NULL) {
9267             free(gameInfo.event);
9268         }
9269         gameInfo.event = StrSave(yy_text);
9270     }   
9271
9272     startedFromSetupPosition = FALSE;
9273     while (cm == PGNTag) {
9274         if (appData.debugMode) 
9275           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9276         err = ParsePGNTag(yy_text, &gameInfo);
9277         if (!err) numPGNTags++;
9278
9279         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9280         if(gameInfo.variant != oldVariant) {
9281             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9282             InitPosition(TRUE);
9283             oldVariant = gameInfo.variant;
9284             if (appData.debugMode) 
9285               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9286         }
9287
9288
9289         if (gameInfo.fen != NULL) {
9290           Board initial_position;
9291           startedFromSetupPosition = TRUE;
9292           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9293             Reset(TRUE, TRUE);
9294             DisplayError(_("Bad FEN position in file"), 0);
9295             return FALSE;
9296           }
9297           CopyBoard(boards[0], initial_position);
9298           if (blackPlaysFirst) {
9299             currentMove = forwardMostMove = backwardMostMove = 1;
9300             CopyBoard(boards[1], initial_position);
9301             strcpy(moveList[0], "");
9302             strcpy(parseList[0], "");
9303             timeRemaining[0][1] = whiteTimeRemaining;
9304             timeRemaining[1][1] = blackTimeRemaining;
9305             if (commentList[0] != NULL) {
9306               commentList[1] = commentList[0];
9307               commentList[0] = NULL;
9308             }
9309           } else {
9310             currentMove = forwardMostMove = backwardMostMove = 0;
9311           }
9312           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9313           {   int i;
9314               initialRulePlies = FENrulePlies;
9315               epStatus[forwardMostMove] = FENepStatus;
9316               for( i=0; i< nrCastlingRights; i++ )
9317                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9318           }
9319           yyboardindex = forwardMostMove;
9320           free(gameInfo.fen);
9321           gameInfo.fen = NULL;
9322         }
9323
9324         yyboardindex = forwardMostMove;
9325         cm = (ChessMove) yylex();
9326
9327         /* Handle comments interspersed among the tags */
9328         while (cm == Comment) {
9329             char *p;
9330             if (appData.debugMode) 
9331               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9332             p = yy_text;
9333             if (*p == '{' || *p == '[' || *p == '(') {
9334                 p[strlen(p) - 1] = NULLCHAR;
9335                 p++;
9336             }
9337             while (*p == '\n') p++;
9338             AppendComment(currentMove, p);
9339             yyboardindex = forwardMostMove;
9340             cm = (ChessMove) yylex();
9341         }
9342     }
9343
9344     /* don't rely on existence of Event tag since if game was
9345      * pasted from clipboard the Event tag may not exist
9346      */
9347     if (numPGNTags > 0){
9348         char *tags;
9349         if (gameInfo.variant == VariantNormal) {
9350           gameInfo.variant = StringToVariant(gameInfo.event);
9351         }
9352         if (!matchMode) {
9353           if( appData.autoDisplayTags ) {
9354             tags = PGNTags(&gameInfo);
9355             TagsPopUp(tags, CmailMsg());
9356             free(tags);
9357           }
9358         }
9359     } else {
9360         /* Make something up, but don't display it now */
9361         SetGameInfo();
9362         TagsPopDown();
9363     }
9364
9365     if (cm == PositionDiagram) {
9366         int i, j;
9367         char *p;
9368         Board initial_position;
9369
9370         if (appData.debugMode)
9371           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9372
9373         if (!startedFromSetupPosition) {
9374             p = yy_text;
9375             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9376               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9377                 switch (*p) {
9378                   case '[':
9379                   case '-':
9380                   case ' ':
9381                   case '\t':
9382                   case '\n':
9383                   case '\r':
9384                     break;
9385                   default:
9386                     initial_position[i][j++] = CharToPiece(*p);
9387                     break;
9388                 }
9389             while (*p == ' ' || *p == '\t' ||
9390                    *p == '\n' || *p == '\r') p++;
9391         
9392             if (strncmp(p, "black", strlen("black"))==0)
9393               blackPlaysFirst = TRUE;
9394             else
9395               blackPlaysFirst = FALSE;
9396             startedFromSetupPosition = TRUE;
9397         
9398             CopyBoard(boards[0], initial_position);
9399             if (blackPlaysFirst) {
9400                 currentMove = forwardMostMove = backwardMostMove = 1;
9401                 CopyBoard(boards[1], initial_position);
9402                 strcpy(moveList[0], "");
9403                 strcpy(parseList[0], "");
9404                 timeRemaining[0][1] = whiteTimeRemaining;
9405                 timeRemaining[1][1] = blackTimeRemaining;
9406                 if (commentList[0] != NULL) {
9407                     commentList[1] = commentList[0];
9408                     commentList[0] = NULL;
9409                 }
9410             } else {
9411                 currentMove = forwardMostMove = backwardMostMove = 0;
9412             }
9413         }
9414         yyboardindex = forwardMostMove;
9415         cm = (ChessMove) yylex();
9416     }
9417
9418     if (first.pr == NoProc) {
9419         StartChessProgram(&first);
9420     }
9421     InitChessProgram(&first, FALSE);
9422     SendToProgram("force\n", &first);
9423     if (startedFromSetupPosition) {
9424         SendBoard(&first, forwardMostMove);
9425     if (appData.debugMode) {
9426         fprintf(debugFP, "Load Game\n");
9427     }
9428         DisplayBothClocks();
9429     }      
9430
9431     /* [HGM] server: flag to write setup moves in broadcast file as one */
9432     loadFlag = appData.suppressLoadMoves;
9433
9434     while (cm == Comment) {
9435         char *p;
9436         if (appData.debugMode) 
9437           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9438         p = yy_text;
9439         if (*p == '{' || *p == '[' || *p == '(') {
9440             p[strlen(p) - 1] = NULLCHAR;
9441             p++;
9442         }
9443         while (*p == '\n') p++;
9444         AppendComment(currentMove, p);
9445         yyboardindex = forwardMostMove;
9446         cm = (ChessMove) yylex();
9447     }
9448
9449     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9450         cm == WhiteWins || cm == BlackWins ||
9451         cm == GameIsDrawn || cm == GameUnfinished) {
9452         DisplayMessage("", _("No moves in game"));
9453         if (cmailMsgLoaded) {
9454             if (appData.debugMode)
9455               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9456             ClearHighlights();
9457             flipView = FALSE;
9458         }
9459         DrawPosition(FALSE, boards[currentMove]);
9460         DisplayBothClocks();
9461         gameMode = EditGame;
9462         ModeHighlight();
9463         gameFileFP = NULL;
9464         cmailOldMove = 0;
9465         return TRUE;
9466     }
9467
9468     // [HGM] PV info: routine tests if comment empty
9469     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9470         DisplayComment(currentMove - 1, commentList[currentMove]);
9471     }
9472     if (!matchMode && appData.timeDelay != 0) 
9473       DrawPosition(FALSE, boards[currentMove]);
9474
9475     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9476       programStats.ok_to_send = 1;
9477     }
9478
9479     /* if the first token after the PGN tags is a move
9480      * and not move number 1, retrieve it from the parser 
9481      */
9482     if (cm != MoveNumberOne)
9483         LoadGameOneMove(cm);
9484
9485     /* load the remaining moves from the file */
9486     while (LoadGameOneMove((ChessMove)0)) {
9487       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9488       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9489     }
9490
9491     /* rewind to the start of the game */
9492     currentMove = backwardMostMove;
9493
9494     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9495
9496     if (oldGameMode == AnalyzeFile ||
9497         oldGameMode == AnalyzeMode) {
9498       AnalyzeFileEvent();
9499     }
9500
9501     if (matchMode || appData.timeDelay == 0) {
9502       ToEndEvent();
9503       gameMode = EditGame;
9504       ModeHighlight();
9505     } else if (appData.timeDelay > 0) {
9506       AutoPlayGameLoop();
9507     }
9508
9509     if (appData.debugMode) 
9510         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9511
9512     loadFlag = 0; /* [HGM] true game starts */
9513     return TRUE;
9514 }
9515
9516 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9517 int
9518 ReloadPosition(offset)
9519      int offset;
9520 {
9521     int positionNumber = lastLoadPositionNumber + offset;
9522     if (lastLoadPositionFP == NULL) {
9523         DisplayError(_("No position has been loaded yet"), 0);
9524         return FALSE;
9525     }
9526     if (positionNumber <= 0) {
9527         DisplayError(_("Can't back up any further"), 0);
9528         return FALSE;
9529     }
9530     return LoadPosition(lastLoadPositionFP, positionNumber,
9531                         lastLoadPositionTitle);
9532 }
9533
9534 /* Load the nth position from the given file */
9535 int
9536 LoadPositionFromFile(filename, n, title)
9537      char *filename;
9538      int n;
9539      char *title;
9540 {
9541     FILE *f;
9542     char buf[MSG_SIZ];
9543
9544     if (strcmp(filename, "-") == 0) {
9545         return LoadPosition(stdin, n, "stdin");
9546     } else {
9547         f = fopen(filename, "rb");
9548         if (f == NULL) {
9549             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9550             DisplayError(buf, errno);
9551             return FALSE;
9552         } else {
9553             return LoadPosition(f, n, title);
9554         }
9555     }
9556 }
9557
9558 /* Load the nth position from the given open file, and close it */
9559 int
9560 LoadPosition(f, positionNumber, title)
9561      FILE *f;
9562      int positionNumber;
9563      char *title;
9564 {
9565     char *p, line[MSG_SIZ];
9566     Board initial_position;
9567     int i, j, fenMode, pn;
9568     
9569     if (gameMode == Training )
9570         SetTrainingModeOff();
9571
9572     if (gameMode != BeginningOfGame) {
9573         Reset(FALSE, TRUE);
9574     }
9575     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9576         fclose(lastLoadPositionFP);
9577     }
9578     if (positionNumber == 0) positionNumber = 1;
9579     lastLoadPositionFP = f;
9580     lastLoadPositionNumber = positionNumber;
9581     strcpy(lastLoadPositionTitle, title);
9582     if (first.pr == NoProc) {
9583       StartChessProgram(&first);
9584       InitChessProgram(&first, FALSE);
9585     }    
9586     pn = positionNumber;
9587     if (positionNumber < 0) {
9588         /* Negative position number means to seek to that byte offset */
9589         if (fseek(f, -positionNumber, 0) == -1) {
9590             DisplayError(_("Can't seek on position file"), 0);
9591             return FALSE;
9592         };
9593         pn = 1;
9594     } else {
9595         if (fseek(f, 0, 0) == -1) {
9596             if (f == lastLoadPositionFP ?
9597                 positionNumber == lastLoadPositionNumber + 1 :
9598                 positionNumber == 1) {
9599                 pn = 1;
9600             } else {
9601                 DisplayError(_("Can't seek on position file"), 0);
9602                 return FALSE;
9603             }
9604         }
9605     }
9606     /* See if this file is FEN or old-style xboard */
9607     if (fgets(line, MSG_SIZ, f) == NULL) {
9608         DisplayError(_("Position not found in file"), 0);
9609         return FALSE;
9610     }
9611     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9612     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9613
9614     if (pn >= 2) {
9615         if (fenMode || line[0] == '#') pn--;
9616         while (pn > 0) {
9617             /* skip positions before number pn */
9618             if (fgets(line, MSG_SIZ, f) == NULL) {
9619                 Reset(TRUE, TRUE);
9620                 DisplayError(_("Position not found in file"), 0);
9621                 return FALSE;
9622             }
9623             if (fenMode || line[0] == '#') pn--;
9624         }
9625     }
9626
9627     if (fenMode) {
9628         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9629             DisplayError(_("Bad FEN position in file"), 0);
9630             return FALSE;
9631         }
9632     } else {
9633         (void) fgets(line, MSG_SIZ, f);
9634         (void) fgets(line, MSG_SIZ, f);
9635     
9636         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9637             (void) fgets(line, MSG_SIZ, f);
9638             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9639                 if (*p == ' ')
9640                   continue;
9641                 initial_position[i][j++] = CharToPiece(*p);
9642             }
9643         }
9644     
9645         blackPlaysFirst = FALSE;
9646         if (!feof(f)) {
9647             (void) fgets(line, MSG_SIZ, f);
9648             if (strncmp(line, "black", strlen("black"))==0)
9649               blackPlaysFirst = TRUE;
9650         }
9651     }
9652     startedFromSetupPosition = TRUE;
9653     
9654     SendToProgram("force\n", &first);
9655     CopyBoard(boards[0], initial_position);
9656     if (blackPlaysFirst) {
9657         currentMove = forwardMostMove = backwardMostMove = 1;
9658         strcpy(moveList[0], "");
9659         strcpy(parseList[0], "");
9660         CopyBoard(boards[1], initial_position);
9661         DisplayMessage("", _("Black to play"));
9662     } else {
9663         currentMove = forwardMostMove = backwardMostMove = 0;
9664         DisplayMessage("", _("White to play"));
9665     }
9666           /* [HGM] copy FEN attributes as well */
9667           {   int i;
9668               initialRulePlies = FENrulePlies;
9669               epStatus[forwardMostMove] = FENepStatus;
9670               for( i=0; i< nrCastlingRights; i++ )
9671                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9672           }
9673     SendBoard(&first, forwardMostMove);
9674     if (appData.debugMode) {
9675 int i, j;
9676   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9677   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9678         fprintf(debugFP, "Load Position\n");
9679     }
9680
9681     if (positionNumber > 1) {
9682         sprintf(line, "%s %d", title, positionNumber);
9683         DisplayTitle(line);
9684     } else {
9685         DisplayTitle(title);
9686     }
9687     gameMode = EditGame;
9688     ModeHighlight();
9689     ResetClocks();
9690     timeRemaining[0][1] = whiteTimeRemaining;
9691     timeRemaining[1][1] = blackTimeRemaining;
9692     DrawPosition(FALSE, boards[currentMove]);
9693    
9694     return TRUE;
9695 }
9696
9697
9698 void
9699 CopyPlayerNameIntoFileName(dest, src)
9700      char **dest, *src;
9701 {
9702     while (*src != NULLCHAR && *src != ',') {
9703         if (*src == ' ') {
9704             *(*dest)++ = '_';
9705             src++;
9706         } else {
9707             *(*dest)++ = *src++;
9708         }
9709     }
9710 }
9711
9712 char *DefaultFileName(ext)
9713      char *ext;
9714 {
9715     static char def[MSG_SIZ];
9716     char *p;
9717
9718     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9719         p = def;
9720         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9721         *p++ = '-';
9722         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9723         *p++ = '.';
9724         strcpy(p, ext);
9725     } else {
9726         def[0] = NULLCHAR;
9727     }
9728     return def;
9729 }
9730
9731 /* Save the current game to the given file */
9732 int
9733 SaveGameToFile(filename, append)
9734      char *filename;
9735      int append;
9736 {
9737     FILE *f;
9738     char buf[MSG_SIZ];
9739
9740     if (strcmp(filename, "-") == 0) {
9741         return SaveGame(stdout, 0, NULL);
9742     } else {
9743         f = fopen(filename, append ? "a" : "w");
9744         if (f == NULL) {
9745             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9746             DisplayError(buf, errno);
9747             return FALSE;
9748         } else {
9749             return SaveGame(f, 0, NULL);
9750         }
9751     }
9752 }
9753
9754 char *
9755 SavePart(str)
9756      char *str;
9757 {
9758     static char buf[MSG_SIZ];
9759     char *p;
9760     
9761     p = strchr(str, ' ');
9762     if (p == NULL) return str;
9763     strncpy(buf, str, p - str);
9764     buf[p - str] = NULLCHAR;
9765     return buf;
9766 }
9767
9768 #define PGN_MAX_LINE 75
9769
9770 #define PGN_SIDE_WHITE  0
9771 #define PGN_SIDE_BLACK  1
9772
9773 /* [AS] */
9774 static int FindFirstMoveOutOfBook( int side )
9775 {
9776     int result = -1;
9777
9778     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9779         int index = backwardMostMove;
9780         int has_book_hit = 0;
9781
9782         if( (index % 2) != side ) {
9783             index++;
9784         }
9785
9786         while( index < forwardMostMove ) {
9787             /* Check to see if engine is in book */
9788             int depth = pvInfoList[index].depth;
9789             int score = pvInfoList[index].score;
9790             int in_book = 0;
9791
9792             if( depth <= 2 ) {
9793                 in_book = 1;
9794             }
9795             else if( score == 0 && depth == 63 ) {
9796                 in_book = 1; /* Zappa */
9797             }
9798             else if( score == 2 && depth == 99 ) {
9799                 in_book = 1; /* Abrok */
9800             }
9801
9802             has_book_hit += in_book;
9803
9804             if( ! in_book ) {
9805                 result = index;
9806
9807                 break;
9808             }
9809
9810             index += 2;
9811         }
9812     }
9813
9814     return result;
9815 }
9816
9817 /* [AS] */
9818 void GetOutOfBookInfo( char * buf )
9819 {
9820     int oob[2];
9821     int i;
9822     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9823
9824     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9825     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9826
9827     *buf = '\0';
9828
9829     if( oob[0] >= 0 || oob[1] >= 0 ) {
9830         for( i=0; i<2; i++ ) {
9831             int idx = oob[i];
9832
9833             if( idx >= 0 ) {
9834                 if( i > 0 && oob[0] >= 0 ) {
9835                     strcat( buf, "   " );
9836                 }
9837
9838                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9839                 sprintf( buf+strlen(buf), "%s%.2f", 
9840                     pvInfoList[idx].score >= 0 ? "+" : "",
9841                     pvInfoList[idx].score / 100.0 );
9842             }
9843         }
9844     }
9845 }
9846
9847 /* Save game in PGN style and close the file */
9848 int
9849 SaveGamePGN(f)
9850      FILE *f;
9851 {
9852     int i, offset, linelen, newblock;
9853     time_t tm;
9854 //    char *movetext;
9855     char numtext[32];
9856     int movelen, numlen, blank;
9857     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9858
9859     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9860     
9861     tm = time((time_t *) NULL);
9862     
9863     PrintPGNTags(f, &gameInfo);
9864     
9865     if (backwardMostMove > 0 || startedFromSetupPosition) {
9866         char *fen = PositionToFEN(backwardMostMove, NULL);
9867         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9868         fprintf(f, "\n{--------------\n");
9869         PrintPosition(f, backwardMostMove);
9870         fprintf(f, "--------------}\n");
9871         free(fen);
9872     }
9873     else {
9874         /* [AS] Out of book annotation */
9875         if( appData.saveOutOfBookInfo ) {
9876             char buf[64];
9877
9878             GetOutOfBookInfo( buf );
9879
9880             if( buf[0] != '\0' ) {
9881                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9882             }
9883         }
9884
9885         fprintf(f, "\n");
9886     }
9887
9888     i = backwardMostMove;
9889     linelen = 0;
9890     newblock = TRUE;
9891
9892     while (i < forwardMostMove) {
9893         /* Print comments preceding this move */
9894         if (commentList[i] != NULL) {
9895             if (linelen > 0) fprintf(f, "\n");
9896             fprintf(f, "{\n%s}\n", commentList[i]);
9897             linelen = 0;
9898             newblock = TRUE;
9899         }
9900
9901         /* Format move number */
9902         if ((i % 2) == 0) {
9903             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9904         } else {
9905             if (newblock) {
9906                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9907             } else {
9908                 numtext[0] = NULLCHAR;
9909             }
9910         }
9911         numlen = strlen(numtext);
9912         newblock = FALSE;
9913
9914         /* Print move number */
9915         blank = linelen > 0 && numlen > 0;
9916         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9917             fprintf(f, "\n");
9918             linelen = 0;
9919             blank = 0;
9920         }
9921         if (blank) {
9922             fprintf(f, " ");
9923             linelen++;
9924         }
9925         fprintf(f, "%s", numtext);
9926         linelen += numlen;
9927
9928         /* Get move */
9929         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9930         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9931
9932         /* Print move */
9933         blank = linelen > 0 && movelen > 0;
9934         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9935             fprintf(f, "\n");
9936             linelen = 0;
9937             blank = 0;
9938         }
9939         if (blank) {
9940             fprintf(f, " ");
9941             linelen++;
9942         }
9943         fprintf(f, "%s", move_buffer);
9944         linelen += movelen;
9945
9946         /* [AS] Add PV info if present */
9947         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9948             /* [HGM] add time */
9949             char buf[MSG_SIZ]; int seconds;
9950
9951             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9952
9953             if( seconds <= 0) buf[0] = 0; else
9954             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9955                 seconds = (seconds + 4)/10; // round to full seconds
9956                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9957                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9958             }
9959
9960             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9961                 pvInfoList[i].score >= 0 ? "+" : "",
9962                 pvInfoList[i].score / 100.0,
9963                 pvInfoList[i].depth,
9964                 buf );
9965
9966             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9967
9968             /* Print score/depth */
9969             blank = linelen > 0 && movelen > 0;
9970             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9971                 fprintf(f, "\n");
9972                 linelen = 0;
9973                 blank = 0;
9974             }
9975             if (blank) {
9976                 fprintf(f, " ");
9977                 linelen++;
9978             }
9979             fprintf(f, "%s", move_buffer);
9980             linelen += movelen;
9981         }
9982
9983         i++;
9984     }
9985     
9986     /* Start a new line */
9987     if (linelen > 0) fprintf(f, "\n");
9988
9989     /* Print comments after last move */
9990     if (commentList[i] != NULL) {
9991         fprintf(f, "{\n%s}\n", commentList[i]);
9992     }
9993
9994     /* Print result */
9995     if (gameInfo.resultDetails != NULL &&
9996         gameInfo.resultDetails[0] != NULLCHAR) {
9997         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9998                 PGNResult(gameInfo.result));
9999     } else {
10000         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10001     }
10002
10003     fclose(f);
10004     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10005     return TRUE;
10006 }
10007
10008 /* Save game in old style and close the file */
10009 int
10010 SaveGameOldStyle(f)
10011      FILE *f;
10012 {
10013     int i, offset;
10014     time_t tm;
10015     
10016     tm = time((time_t *) NULL);
10017     
10018     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10019     PrintOpponents(f);
10020     
10021     if (backwardMostMove > 0 || startedFromSetupPosition) {
10022         fprintf(f, "\n[--------------\n");
10023         PrintPosition(f, backwardMostMove);
10024         fprintf(f, "--------------]\n");
10025     } else {
10026         fprintf(f, "\n");
10027     }
10028
10029     i = backwardMostMove;
10030     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10031
10032     while (i < forwardMostMove) {
10033         if (commentList[i] != NULL) {
10034             fprintf(f, "[%s]\n", commentList[i]);
10035         }
10036
10037         if ((i % 2) == 1) {
10038             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10039             i++;
10040         } else {
10041             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10042             i++;
10043             if (commentList[i] != NULL) {
10044                 fprintf(f, "\n");
10045                 continue;
10046             }
10047             if (i >= forwardMostMove) {
10048                 fprintf(f, "\n");
10049                 break;
10050             }
10051             fprintf(f, "%s\n", parseList[i]);
10052             i++;
10053         }
10054     }
10055     
10056     if (commentList[i] != NULL) {
10057         fprintf(f, "[%s]\n", commentList[i]);
10058     }
10059
10060     /* This isn't really the old style, but it's close enough */
10061     if (gameInfo.resultDetails != NULL &&
10062         gameInfo.resultDetails[0] != NULLCHAR) {
10063         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10064                 gameInfo.resultDetails);
10065     } else {
10066         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10067     }
10068
10069     fclose(f);
10070     return TRUE;
10071 }
10072
10073 /* Save the current game to open file f and close the file */
10074 int
10075 SaveGame(f, dummy, dummy2)
10076      FILE *f;
10077      int dummy;
10078      char *dummy2;
10079 {
10080     if (gameMode == EditPosition) EditPositionDone(TRUE);
10081     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10082     if (appData.oldSaveStyle)
10083       return SaveGameOldStyle(f);
10084     else
10085       return SaveGamePGN(f);
10086 }
10087
10088 /* Save the current position to the given file */
10089 int
10090 SavePositionToFile(filename)
10091      char *filename;
10092 {
10093     FILE *f;
10094     char buf[MSG_SIZ];
10095
10096     if (strcmp(filename, "-") == 0) {
10097         return SavePosition(stdout, 0, NULL);
10098     } else {
10099         f = fopen(filename, "a");
10100         if (f == NULL) {
10101             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10102             DisplayError(buf, errno);
10103             return FALSE;
10104         } else {
10105             SavePosition(f, 0, NULL);
10106             return TRUE;
10107         }
10108     }
10109 }
10110
10111 /* Save the current position to the given open file and close the file */
10112 int
10113 SavePosition(f, dummy, dummy2)
10114      FILE *f;
10115      int dummy;
10116      char *dummy2;
10117 {
10118     time_t tm;
10119     char *fen;
10120
10121     if (gameMode == EditPosition) EditPositionDone(TRUE);
10122     if (appData.oldSaveStyle) {
10123         tm = time((time_t *) NULL);
10124     
10125         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10126         PrintOpponents(f);
10127         fprintf(f, "[--------------\n");
10128         PrintPosition(f, currentMove);
10129         fprintf(f, "--------------]\n");
10130     } else {
10131         fen = PositionToFEN(currentMove, NULL);
10132         fprintf(f, "%s\n", fen);
10133         free(fen);
10134     }
10135     fclose(f);
10136     return TRUE;
10137 }
10138
10139 void
10140 ReloadCmailMsgEvent(unregister)
10141      int unregister;
10142 {
10143 #if !WIN32
10144     static char *inFilename = NULL;
10145     static char *outFilename;
10146     int i;
10147     struct stat inbuf, outbuf;
10148     int status;
10149     
10150     /* Any registered moves are unregistered if unregister is set, */
10151     /* i.e. invoked by the signal handler */
10152     if (unregister) {
10153         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10154             cmailMoveRegistered[i] = FALSE;
10155             if (cmailCommentList[i] != NULL) {
10156                 free(cmailCommentList[i]);
10157                 cmailCommentList[i] = NULL;
10158             }
10159         }
10160         nCmailMovesRegistered = 0;
10161     }
10162
10163     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10164         cmailResult[i] = CMAIL_NOT_RESULT;
10165     }
10166     nCmailResults = 0;
10167
10168     if (inFilename == NULL) {
10169         /* Because the filenames are static they only get malloced once  */
10170         /* and they never get freed                                      */
10171         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10172         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10173
10174         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10175         sprintf(outFilename, "%s.out", appData.cmailGameName);
10176     }
10177     
10178     status = stat(outFilename, &outbuf);
10179     if (status < 0) {
10180         cmailMailedMove = FALSE;
10181     } else {
10182         status = stat(inFilename, &inbuf);
10183         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10184     }
10185     
10186     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10187        counts the games, notes how each one terminated, etc.
10188        
10189        It would be nice to remove this kludge and instead gather all
10190        the information while building the game list.  (And to keep it
10191        in the game list nodes instead of having a bunch of fixed-size
10192        parallel arrays.)  Note this will require getting each game's
10193        termination from the PGN tags, as the game list builder does
10194        not process the game moves.  --mann
10195        */
10196     cmailMsgLoaded = TRUE;
10197     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10198     
10199     /* Load first game in the file or popup game menu */
10200     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10201
10202 #endif /* !WIN32 */
10203     return;
10204 }
10205
10206 int
10207 RegisterMove()
10208 {
10209     FILE *f;
10210     char string[MSG_SIZ];
10211
10212     if (   cmailMailedMove
10213         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10214         return TRUE;            /* Allow free viewing  */
10215     }
10216
10217     /* Unregister move to ensure that we don't leave RegisterMove        */
10218     /* with the move registered when the conditions for registering no   */
10219     /* longer hold                                                       */
10220     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10221         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10222         nCmailMovesRegistered --;
10223
10224         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10225           {
10226               free(cmailCommentList[lastLoadGameNumber - 1]);
10227               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10228           }
10229     }
10230
10231     if (cmailOldMove == -1) {
10232         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10233         return FALSE;
10234     }
10235
10236     if (currentMove > cmailOldMove + 1) {
10237         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10238         return FALSE;
10239     }
10240
10241     if (currentMove < cmailOldMove) {
10242         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10243         return FALSE;
10244     }
10245
10246     if (forwardMostMove > currentMove) {
10247         /* Silently truncate extra moves */
10248         TruncateGame();
10249     }
10250
10251     if (   (currentMove == cmailOldMove + 1)
10252         || (   (currentMove == cmailOldMove)
10253             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10254                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10255         if (gameInfo.result != GameUnfinished) {
10256             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10257         }
10258
10259         if (commentList[currentMove] != NULL) {
10260             cmailCommentList[lastLoadGameNumber - 1]
10261               = StrSave(commentList[currentMove]);
10262         }
10263         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10264
10265         if (appData.debugMode)
10266           fprintf(debugFP, "Saving %s for game %d\n",
10267                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10268
10269         sprintf(string,
10270                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10271         
10272         f = fopen(string, "w");
10273         if (appData.oldSaveStyle) {
10274             SaveGameOldStyle(f); /* also closes the file */
10275             
10276             sprintf(string, "%s.pos.out", appData.cmailGameName);
10277             f = fopen(string, "w");
10278             SavePosition(f, 0, NULL); /* also closes the file */
10279         } else {
10280             fprintf(f, "{--------------\n");
10281             PrintPosition(f, currentMove);
10282             fprintf(f, "--------------}\n\n");
10283             
10284             SaveGame(f, 0, NULL); /* also closes the file*/
10285         }
10286         
10287         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10288         nCmailMovesRegistered ++;
10289     } else if (nCmailGames == 1) {
10290         DisplayError(_("You have not made a move yet"), 0);
10291         return FALSE;
10292     }
10293
10294     return TRUE;
10295 }
10296
10297 void
10298 MailMoveEvent()
10299 {
10300 #if !WIN32
10301     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10302     FILE *commandOutput;
10303     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10304     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10305     int nBuffers;
10306     int i;
10307     int archived;
10308     char *arcDir;
10309
10310     if (! cmailMsgLoaded) {
10311         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10312         return;
10313     }
10314
10315     if (nCmailGames == nCmailResults) {
10316         DisplayError(_("No unfinished games"), 0);
10317         return;
10318     }
10319
10320 #if CMAIL_PROHIBIT_REMAIL
10321     if (cmailMailedMove) {
10322         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);
10323         DisplayError(msg, 0);
10324         return;
10325     }
10326 #endif
10327
10328     if (! (cmailMailedMove || RegisterMove())) return;
10329     
10330     if (   cmailMailedMove
10331         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10332         sprintf(string, partCommandString,
10333                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10334         commandOutput = popen(string, "r");
10335
10336         if (commandOutput == NULL) {
10337             DisplayError(_("Failed to invoke cmail"), 0);
10338         } else {
10339             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10340                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10341             }
10342             if (nBuffers > 1) {
10343                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10344                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10345                 nBytes = MSG_SIZ - 1;
10346             } else {
10347                 (void) memcpy(msg, buffer, nBytes);
10348             }
10349             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10350
10351             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10352                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10353
10354                 archived = TRUE;
10355                 for (i = 0; i < nCmailGames; i ++) {
10356                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10357                         archived = FALSE;
10358                     }
10359                 }
10360                 if (   archived
10361                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10362                         != NULL)) {
10363                     sprintf(buffer, "%s/%s.%s.archive",
10364                             arcDir,
10365                             appData.cmailGameName,
10366                             gameInfo.date);
10367                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10368                     cmailMsgLoaded = FALSE;
10369                 }
10370             }
10371
10372             DisplayInformation(msg);
10373             pclose(commandOutput);
10374         }
10375     } else {
10376         if ((*cmailMsg) != '\0') {
10377             DisplayInformation(cmailMsg);
10378         }
10379     }
10380
10381     return;
10382 #endif /* !WIN32 */
10383 }
10384
10385 char *
10386 CmailMsg()
10387 {
10388 #if WIN32
10389     return NULL;
10390 #else
10391     int  prependComma = 0;
10392     char number[5];
10393     char string[MSG_SIZ];       /* Space for game-list */
10394     int  i;
10395     
10396     if (!cmailMsgLoaded) return "";
10397
10398     if (cmailMailedMove) {
10399         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10400     } else {
10401         /* Create a list of games left */
10402         sprintf(string, "[");
10403         for (i = 0; i < nCmailGames; i ++) {
10404             if (! (   cmailMoveRegistered[i]
10405                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10406                 if (prependComma) {
10407                     sprintf(number, ",%d", i + 1);
10408                 } else {
10409                     sprintf(number, "%d", i + 1);
10410                     prependComma = 1;
10411                 }
10412                 
10413                 strcat(string, number);
10414             }
10415         }
10416         strcat(string, "]");
10417
10418         if (nCmailMovesRegistered + nCmailResults == 0) {
10419             switch (nCmailGames) {
10420               case 1:
10421                 sprintf(cmailMsg,
10422                         _("Still need to make move for game\n"));
10423                 break;
10424                 
10425               case 2:
10426                 sprintf(cmailMsg,
10427                         _("Still need to make moves for both games\n"));
10428                 break;
10429                 
10430               default:
10431                 sprintf(cmailMsg,
10432                         _("Still need to make moves for all %d games\n"),
10433                         nCmailGames);
10434                 break;
10435             }
10436         } else {
10437             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10438               case 1:
10439                 sprintf(cmailMsg,
10440                         _("Still need to make a move for game %s\n"),
10441                         string);
10442                 break;
10443                 
10444               case 0:
10445                 if (nCmailResults == nCmailGames) {
10446                     sprintf(cmailMsg, _("No unfinished games\n"));
10447                 } else {
10448                     sprintf(cmailMsg, _("Ready to send mail\n"));
10449                 }
10450                 break;
10451                 
10452               default:
10453                 sprintf(cmailMsg,
10454                         _("Still need to make moves for games %s\n"),
10455                         string);
10456             }
10457         }
10458     }
10459     return cmailMsg;
10460 #endif /* WIN32 */
10461 }
10462
10463 void
10464 ResetGameEvent()
10465 {
10466     if (gameMode == Training)
10467       SetTrainingModeOff();
10468
10469     Reset(TRUE, TRUE);
10470     cmailMsgLoaded = FALSE;
10471     if (appData.icsActive) {
10472       SendToICS(ics_prefix);
10473       SendToICS("refresh\n");
10474     }
10475 }
10476
10477 void
10478 ExitEvent(status)
10479      int status;
10480 {
10481     exiting++;
10482     if (exiting > 2) {
10483       /* Give up on clean exit */
10484       exit(status);
10485     }
10486     if (exiting > 1) {
10487       /* Keep trying for clean exit */
10488       return;
10489     }
10490
10491     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10492
10493     if (telnetISR != NULL) {
10494       RemoveInputSource(telnetISR);
10495     }
10496     if (icsPR != NoProc) {
10497       DestroyChildProcess(icsPR, TRUE);
10498     }
10499
10500     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10501     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10502
10503     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10504     /* make sure this other one finishes before killing it!                  */
10505     if(endingGame) { int count = 0;
10506         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10507         while(endingGame && count++ < 10) DoSleep(1);
10508         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10509     }
10510
10511     /* Kill off chess programs */
10512     if (first.pr != NoProc) {
10513         ExitAnalyzeMode();
10514         
10515         DoSleep( appData.delayBeforeQuit );
10516         SendToProgram("quit\n", &first);
10517         DoSleep( appData.delayAfterQuit );
10518         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10519     }
10520     if (second.pr != NoProc) {
10521         DoSleep( appData.delayBeforeQuit );
10522         SendToProgram("quit\n", &second);
10523         DoSleep( appData.delayAfterQuit );
10524         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10525     }
10526     if (first.isr != NULL) {
10527         RemoveInputSource(first.isr);
10528     }
10529     if (second.isr != NULL) {
10530         RemoveInputSource(second.isr);
10531     }
10532
10533     ShutDownFrontEnd();
10534     exit(status);
10535 }
10536
10537 void
10538 PauseEvent()
10539 {
10540     if (appData.debugMode)
10541         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10542     if (pausing) {
10543         pausing = FALSE;
10544         ModeHighlight();
10545         if (gameMode == MachinePlaysWhite ||
10546             gameMode == MachinePlaysBlack) {
10547             StartClocks();
10548         } else {
10549             DisplayBothClocks();
10550         }
10551         if (gameMode == PlayFromGameFile) {
10552             if (appData.timeDelay >= 0) 
10553                 AutoPlayGameLoop();
10554         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10555             Reset(FALSE, TRUE);
10556             SendToICS(ics_prefix);
10557             SendToICS("refresh\n");
10558         } else if (currentMove < forwardMostMove) {
10559             ForwardInner(forwardMostMove);
10560         }
10561         pauseExamInvalid = FALSE;
10562     } else {
10563         switch (gameMode) {
10564           default:
10565             return;
10566           case IcsExamining:
10567             pauseExamForwardMostMove = forwardMostMove;
10568             pauseExamInvalid = FALSE;
10569             /* fall through */
10570           case IcsObserving:
10571           case IcsPlayingWhite:
10572           case IcsPlayingBlack:
10573             pausing = TRUE;
10574             ModeHighlight();
10575             return;
10576           case PlayFromGameFile:
10577             (void) StopLoadGameTimer();
10578             pausing = TRUE;
10579             ModeHighlight();
10580             break;
10581           case BeginningOfGame:
10582             if (appData.icsActive) return;
10583             /* else fall through */
10584           case MachinePlaysWhite:
10585           case MachinePlaysBlack:
10586           case TwoMachinesPlay:
10587             if (forwardMostMove == 0)
10588               return;           /* don't pause if no one has moved */
10589             if ((gameMode == MachinePlaysWhite &&
10590                  !WhiteOnMove(forwardMostMove)) ||
10591                 (gameMode == MachinePlaysBlack &&
10592                  WhiteOnMove(forwardMostMove))) {
10593                 StopClocks();
10594             }
10595             pausing = TRUE;
10596             ModeHighlight();
10597             break;
10598         }
10599     }
10600 }
10601
10602 void
10603 EditCommentEvent()
10604 {
10605     char title[MSG_SIZ];
10606
10607     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10608         strcpy(title, _("Edit comment"));
10609     } else {
10610         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10611                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10612                 parseList[currentMove - 1]);
10613     }
10614
10615     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10616 }
10617
10618
10619 void
10620 EditTagsEvent()
10621 {
10622     char *tags = PGNTags(&gameInfo);
10623     EditTagsPopUp(tags);
10624     free(tags);
10625 }
10626
10627 void
10628 AnalyzeModeEvent()
10629 {
10630     if (appData.noChessProgram || gameMode == AnalyzeMode)
10631       return;
10632
10633     if (gameMode != AnalyzeFile) {
10634         if (!appData.icsEngineAnalyze) {
10635                EditGameEvent();
10636                if (gameMode != EditGame) return;
10637         }
10638         ResurrectChessProgram();
10639         SendToProgram("analyze\n", &first);
10640         first.analyzing = TRUE;
10641         /*first.maybeThinking = TRUE;*/
10642         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10643         EngineOutputPopUp();
10644     }
10645     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10646     pausing = FALSE;
10647     ModeHighlight();
10648     SetGameInfo();
10649
10650     StartAnalysisClock();
10651     GetTimeMark(&lastNodeCountTime);
10652     lastNodeCount = 0;
10653 }
10654
10655 void
10656 AnalyzeFileEvent()
10657 {
10658     if (appData.noChessProgram || gameMode == AnalyzeFile)
10659       return;
10660
10661     if (gameMode != AnalyzeMode) {
10662         EditGameEvent();
10663         if (gameMode != EditGame) return;
10664         ResurrectChessProgram();
10665         SendToProgram("analyze\n", &first);
10666         first.analyzing = TRUE;
10667         /*first.maybeThinking = TRUE;*/
10668         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10669         EngineOutputPopUp();
10670     }
10671     gameMode = AnalyzeFile;
10672     pausing = FALSE;
10673     ModeHighlight();
10674     SetGameInfo();
10675
10676     StartAnalysisClock();
10677     GetTimeMark(&lastNodeCountTime);
10678     lastNodeCount = 0;
10679 }
10680
10681 void
10682 MachineWhiteEvent()
10683 {
10684     char buf[MSG_SIZ];
10685     char *bookHit = NULL;
10686
10687     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10688       return;
10689
10690
10691     if (gameMode == PlayFromGameFile || 
10692         gameMode == TwoMachinesPlay  || 
10693         gameMode == Training         || 
10694         gameMode == AnalyzeMode      || 
10695         gameMode == EndOfGame)
10696         EditGameEvent();
10697
10698     if (gameMode == EditPosition) 
10699         EditPositionDone(TRUE);
10700
10701     if (!WhiteOnMove(currentMove)) {
10702         DisplayError(_("It is not White's turn"), 0);
10703         return;
10704     }
10705   
10706     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10707       ExitAnalyzeMode();
10708
10709     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10710         gameMode == AnalyzeFile)
10711         TruncateGame();
10712
10713     ResurrectChessProgram();    /* in case it isn't running */
10714     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10715         gameMode = MachinePlaysWhite;
10716         ResetClocks();
10717     } else
10718     gameMode = MachinePlaysWhite;
10719     pausing = FALSE;
10720     ModeHighlight();
10721     SetGameInfo();
10722     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10723     DisplayTitle(buf);
10724     if (first.sendName) {
10725       sprintf(buf, "name %s\n", gameInfo.black);
10726       SendToProgram(buf, &first);
10727     }
10728     if (first.sendTime) {
10729       if (first.useColors) {
10730         SendToProgram("black\n", &first); /*gnu kludge*/
10731       }
10732       SendTimeRemaining(&first, TRUE);
10733     }
10734     if (first.useColors) {
10735       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10736     }
10737     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10738     SetMachineThinkingEnables();
10739     first.maybeThinking = TRUE;
10740     StartClocks();
10741     firstMove = FALSE;
10742
10743     if (appData.autoFlipView && !flipView) {
10744       flipView = !flipView;
10745       DrawPosition(FALSE, NULL);
10746       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10747     }
10748
10749     if(bookHit) { // [HGM] book: simulate book reply
10750         static char bookMove[MSG_SIZ]; // a bit generous?
10751
10752         programStats.nodes = programStats.depth = programStats.time = 
10753         programStats.score = programStats.got_only_move = 0;
10754         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10755
10756         strcpy(bookMove, "move ");
10757         strcat(bookMove, bookHit);
10758         HandleMachineMove(bookMove, &first);
10759     }
10760 }
10761
10762 void
10763 MachineBlackEvent()
10764 {
10765     char buf[MSG_SIZ];
10766    char *bookHit = NULL;
10767
10768     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10769         return;
10770
10771
10772     if (gameMode == PlayFromGameFile || 
10773         gameMode == TwoMachinesPlay  || 
10774         gameMode == Training         || 
10775         gameMode == AnalyzeMode      || 
10776         gameMode == EndOfGame)
10777         EditGameEvent();
10778
10779     if (gameMode == EditPosition) 
10780         EditPositionDone(TRUE);
10781
10782     if (WhiteOnMove(currentMove)) {
10783         DisplayError(_("It is not Black's turn"), 0);
10784         return;
10785     }
10786     
10787     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10788       ExitAnalyzeMode();
10789
10790     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10791         gameMode == AnalyzeFile)
10792         TruncateGame();
10793
10794     ResurrectChessProgram();    /* in case it isn't running */
10795     gameMode = MachinePlaysBlack;
10796     pausing = FALSE;
10797     ModeHighlight();
10798     SetGameInfo();
10799     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10800     DisplayTitle(buf);
10801     if (first.sendName) {
10802       sprintf(buf, "name %s\n", gameInfo.white);
10803       SendToProgram(buf, &first);
10804     }
10805     if (first.sendTime) {
10806       if (first.useColors) {
10807         SendToProgram("white\n", &first); /*gnu kludge*/
10808       }
10809       SendTimeRemaining(&first, FALSE);
10810     }
10811     if (first.useColors) {
10812       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10813     }
10814     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10815     SetMachineThinkingEnables();
10816     first.maybeThinking = TRUE;
10817     StartClocks();
10818
10819     if (appData.autoFlipView && flipView) {
10820       flipView = !flipView;
10821       DrawPosition(FALSE, NULL);
10822       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10823     }
10824     if(bookHit) { // [HGM] book: simulate book reply
10825         static char bookMove[MSG_SIZ]; // a bit generous?
10826
10827         programStats.nodes = programStats.depth = programStats.time = 
10828         programStats.score = programStats.got_only_move = 0;
10829         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10830
10831         strcpy(bookMove, "move ");
10832         strcat(bookMove, bookHit);
10833         HandleMachineMove(bookMove, &first);
10834     }
10835 }
10836
10837
10838 void
10839 DisplayTwoMachinesTitle()
10840 {
10841     char buf[MSG_SIZ];
10842     if (appData.matchGames > 0) {
10843         if (first.twoMachinesColor[0] == 'w') {
10844             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10845                     gameInfo.white, gameInfo.black,
10846                     first.matchWins, second.matchWins,
10847                     matchGame - 1 - (first.matchWins + second.matchWins));
10848         } else {
10849             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10850                     gameInfo.white, gameInfo.black,
10851                     second.matchWins, first.matchWins,
10852                     matchGame - 1 - (first.matchWins + second.matchWins));
10853         }
10854     } else {
10855         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10856     }
10857     DisplayTitle(buf);
10858 }
10859
10860 void
10861 TwoMachinesEvent P((void))
10862 {
10863     int i;
10864     char buf[MSG_SIZ];
10865     ChessProgramState *onmove;
10866     char *bookHit = NULL;
10867     
10868     if (appData.noChessProgram) return;
10869
10870     switch (gameMode) {
10871       case TwoMachinesPlay:
10872         return;
10873       case MachinePlaysWhite:
10874       case MachinePlaysBlack:
10875         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10876             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10877             return;
10878         }
10879         /* fall through */
10880       case BeginningOfGame:
10881       case PlayFromGameFile:
10882       case EndOfGame:
10883         EditGameEvent();
10884         if (gameMode != EditGame) return;
10885         break;
10886       case EditPosition:
10887         EditPositionDone(TRUE);
10888         break;
10889       case AnalyzeMode:
10890       case AnalyzeFile:
10891         ExitAnalyzeMode();
10892         break;
10893       case EditGame:
10894       default:
10895         break;
10896     }
10897
10898     forwardMostMove = currentMove;
10899     ResurrectChessProgram();    /* in case first program isn't running */
10900
10901     if (second.pr == NULL) {
10902         StartChessProgram(&second);
10903         if (second.protocolVersion == 1) {
10904           TwoMachinesEventIfReady();
10905         } else {
10906           /* kludge: allow timeout for initial "feature" command */
10907           FreezeUI();
10908           DisplayMessage("", _("Starting second chess program"));
10909           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10910         }
10911         return;
10912     }
10913     DisplayMessage("", "");
10914     InitChessProgram(&second, FALSE);
10915     SendToProgram("force\n", &second);
10916     if (startedFromSetupPosition) {
10917         SendBoard(&second, backwardMostMove);
10918     if (appData.debugMode) {
10919         fprintf(debugFP, "Two Machines\n");
10920     }
10921     }
10922     for (i = backwardMostMove; i < forwardMostMove; i++) {
10923         SendMoveToProgram(i, &second);
10924     }
10925
10926     gameMode = TwoMachinesPlay;
10927     pausing = FALSE;
10928     ModeHighlight();
10929     SetGameInfo();
10930     DisplayTwoMachinesTitle();
10931     firstMove = TRUE;
10932     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10933         onmove = &first;
10934     } else {
10935         onmove = &second;
10936     }
10937
10938     SendToProgram(first.computerString, &first);
10939     if (first.sendName) {
10940       sprintf(buf, "name %s\n", second.tidy);
10941       SendToProgram(buf, &first);
10942     }
10943     SendToProgram(second.computerString, &second);
10944     if (second.sendName) {
10945       sprintf(buf, "name %s\n", first.tidy);
10946       SendToProgram(buf, &second);
10947     }
10948
10949     ResetClocks();
10950     if (!first.sendTime || !second.sendTime) {
10951         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10952         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10953     }
10954     if (onmove->sendTime) {
10955       if (onmove->useColors) {
10956         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10957       }
10958       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10959     }
10960     if (onmove->useColors) {
10961       SendToProgram(onmove->twoMachinesColor, onmove);
10962     }
10963     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10964 //    SendToProgram("go\n", onmove);
10965     onmove->maybeThinking = TRUE;
10966     SetMachineThinkingEnables();
10967
10968     StartClocks();
10969
10970     if(bookHit) { // [HGM] book: simulate book reply
10971         static char bookMove[MSG_SIZ]; // a bit generous?
10972
10973         programStats.nodes = programStats.depth = programStats.time = 
10974         programStats.score = programStats.got_only_move = 0;
10975         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10976
10977         strcpy(bookMove, "move ");
10978         strcat(bookMove, bookHit);
10979         savedMessage = bookMove; // args for deferred call
10980         savedState = onmove;
10981         ScheduleDelayedEvent(DeferredBookMove, 1);
10982     }
10983 }
10984
10985 void
10986 TrainingEvent()
10987 {
10988     if (gameMode == Training) {
10989       SetTrainingModeOff();
10990       gameMode = PlayFromGameFile;
10991       DisplayMessage("", _("Training mode off"));
10992     } else {
10993       gameMode = Training;
10994       animateTraining = appData.animate;
10995
10996       /* make sure we are not already at the end of the game */
10997       if (currentMove < forwardMostMove) {
10998         SetTrainingModeOn();
10999         DisplayMessage("", _("Training mode on"));
11000       } else {
11001         gameMode = PlayFromGameFile;
11002         DisplayError(_("Already at end of game"), 0);
11003       }
11004     }
11005     ModeHighlight();
11006 }
11007
11008 void
11009 IcsClientEvent()
11010 {
11011     if (!appData.icsActive) return;
11012     switch (gameMode) {
11013       case IcsPlayingWhite:
11014       case IcsPlayingBlack:
11015       case IcsObserving:
11016       case IcsIdle:
11017       case BeginningOfGame:
11018       case IcsExamining:
11019         return;
11020
11021       case EditGame:
11022         break;
11023
11024       case EditPosition:
11025         EditPositionDone(TRUE);
11026         break;
11027
11028       case AnalyzeMode:
11029       case AnalyzeFile:
11030         ExitAnalyzeMode();
11031         break;
11032         
11033       default:
11034         EditGameEvent();
11035         break;
11036     }
11037
11038     gameMode = IcsIdle;
11039     ModeHighlight();
11040     return;
11041 }
11042
11043
11044 void
11045 EditGameEvent()
11046 {
11047     int i;
11048
11049     switch (gameMode) {
11050       case Training:
11051         SetTrainingModeOff();
11052         break;
11053       case MachinePlaysWhite:
11054       case MachinePlaysBlack:
11055       case BeginningOfGame:
11056         SendToProgram("force\n", &first);
11057         SetUserThinkingEnables();
11058         break;
11059       case PlayFromGameFile:
11060         (void) StopLoadGameTimer();
11061         if (gameFileFP != NULL) {
11062             gameFileFP = NULL;
11063         }
11064         break;
11065       case EditPosition:
11066         EditPositionDone(TRUE);
11067         break;
11068       case AnalyzeMode:
11069       case AnalyzeFile:
11070         ExitAnalyzeMode();
11071         SendToProgram("force\n", &first);
11072         break;
11073       case TwoMachinesPlay:
11074         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11075         ResurrectChessProgram();
11076         SetUserThinkingEnables();
11077         break;
11078       case EndOfGame:
11079         ResurrectChessProgram();
11080         break;
11081       case IcsPlayingBlack:
11082       case IcsPlayingWhite:
11083         DisplayError(_("Warning: You are still playing a game"), 0);
11084         break;
11085       case IcsObserving:
11086         DisplayError(_("Warning: You are still observing a game"), 0);
11087         break;
11088       case IcsExamining:
11089         DisplayError(_("Warning: You are still examining a game"), 0);
11090         break;
11091       case IcsIdle:
11092         break;
11093       case EditGame:
11094       default:
11095         return;
11096     }
11097     
11098     pausing = FALSE;
11099     StopClocks();
11100     first.offeredDraw = second.offeredDraw = 0;
11101
11102     if (gameMode == PlayFromGameFile) {
11103         whiteTimeRemaining = timeRemaining[0][currentMove];
11104         blackTimeRemaining = timeRemaining[1][currentMove];
11105         DisplayTitle("");
11106     }
11107
11108     if (gameMode == MachinePlaysWhite ||
11109         gameMode == MachinePlaysBlack ||
11110         gameMode == TwoMachinesPlay ||
11111         gameMode == EndOfGame) {
11112         i = forwardMostMove;
11113         while (i > currentMove) {
11114             SendToProgram("undo\n", &first);
11115             i--;
11116         }
11117         whiteTimeRemaining = timeRemaining[0][currentMove];
11118         blackTimeRemaining = timeRemaining[1][currentMove];
11119         DisplayBothClocks();
11120         if (whiteFlag || blackFlag) {
11121             whiteFlag = blackFlag = 0;
11122         }
11123         DisplayTitle("");
11124     }           
11125     
11126     gameMode = EditGame;
11127     ModeHighlight();
11128     SetGameInfo();
11129 }
11130
11131
11132 void
11133 EditPositionEvent()
11134 {
11135     if (gameMode == EditPosition) {
11136         EditGameEvent();
11137         return;
11138     }
11139     
11140     EditGameEvent();
11141     if (gameMode != EditGame) return;
11142     
11143     gameMode = EditPosition;
11144     ModeHighlight();
11145     SetGameInfo();
11146     if (currentMove > 0)
11147       CopyBoard(boards[0], boards[currentMove]);
11148     
11149     blackPlaysFirst = !WhiteOnMove(currentMove);
11150     ResetClocks();
11151     currentMove = forwardMostMove = backwardMostMove = 0;
11152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11153     DisplayMove(-1);
11154 }
11155
11156 void
11157 ExitAnalyzeMode()
11158 {
11159     /* [DM] icsEngineAnalyze - possible call from other functions */
11160     if (appData.icsEngineAnalyze) {
11161         appData.icsEngineAnalyze = FALSE;
11162
11163         DisplayMessage("",_("Close ICS engine analyze..."));
11164     }
11165     if (first.analysisSupport && first.analyzing) {
11166       SendToProgram("exit\n", &first);
11167       first.analyzing = FALSE;
11168     }
11169     thinkOutput[0] = NULLCHAR;
11170 }
11171
11172 void
11173 EditPositionDone(Boolean fakeRights)
11174 {
11175     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11176
11177     startedFromSetupPosition = TRUE;
11178     InitChessProgram(&first, FALSE);
11179     if(fakeRights)  
11180       { /* don't do this if we just pasted FEN */
11181         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11182         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11183           {
11184             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11185             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11186           } 
11187         else 
11188           castlingRights[0][2] = -1;
11189         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11190           {
11191             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11192             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11193           } 
11194         else 
11195           castlingRights[0][5] = -1;
11196       }
11197     SendToProgram("force\n", &first);
11198     if (blackPlaysFirst) {
11199         strcpy(moveList[0], "");
11200         strcpy(parseList[0], "");
11201         currentMove = forwardMostMove = backwardMostMove = 1;
11202         CopyBoard(boards[1], boards[0]);
11203         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11204         { int i;
11205           epStatus[1] = epStatus[0];
11206           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11207         }
11208     } else {
11209         currentMove = forwardMostMove = backwardMostMove = 0;
11210     }
11211     SendBoard(&first, forwardMostMove);
11212     if (appData.debugMode) {
11213         fprintf(debugFP, "EditPosDone\n");
11214     }
11215     DisplayTitle("");
11216     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11217     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11218     gameMode = EditGame;
11219     ModeHighlight();
11220     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11221     ClearHighlights(); /* [AS] */
11222 }
11223
11224 /* Pause for `ms' milliseconds */
11225 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11226 void
11227 TimeDelay(ms)
11228      long ms;
11229 {
11230     TimeMark m1, m2;
11231
11232     GetTimeMark(&m1);
11233     do {
11234         GetTimeMark(&m2);
11235     } while (SubtractTimeMarks(&m2, &m1) < ms);
11236 }
11237
11238 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11239 void
11240 SendMultiLineToICS(buf)
11241      char *buf;
11242 {
11243     char temp[MSG_SIZ+1], *p;
11244     int len;
11245
11246     len = strlen(buf);
11247     if (len > MSG_SIZ)
11248       len = MSG_SIZ;
11249   
11250     strncpy(temp, buf, len);
11251     temp[len] = 0;
11252
11253     p = temp;
11254     while (*p) {
11255         if (*p == '\n' || *p == '\r')
11256           *p = ' ';
11257         ++p;
11258     }
11259
11260     strcat(temp, "\n");
11261     SendToICS(temp);
11262     SendToPlayer(temp, strlen(temp));
11263 }
11264
11265 void
11266 SetWhiteToPlayEvent()
11267 {
11268     if (gameMode == EditPosition) {
11269         blackPlaysFirst = FALSE;
11270         DisplayBothClocks();    /* works because currentMove is 0 */
11271     } else if (gameMode == IcsExamining) {
11272         SendToICS(ics_prefix);
11273         SendToICS("tomove white\n");
11274     }
11275 }
11276
11277 void
11278 SetBlackToPlayEvent()
11279 {
11280     if (gameMode == EditPosition) {
11281         blackPlaysFirst = TRUE;
11282         currentMove = 1;        /* kludge */
11283         DisplayBothClocks();
11284         currentMove = 0;
11285     } else if (gameMode == IcsExamining) {
11286         SendToICS(ics_prefix);
11287         SendToICS("tomove black\n");
11288     }
11289 }
11290
11291 void
11292 EditPositionMenuEvent(selection, x, y)
11293      ChessSquare selection;
11294      int x, y;
11295 {
11296     char buf[MSG_SIZ];
11297     ChessSquare piece = boards[0][y][x];
11298
11299     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11300
11301     switch (selection) {
11302       case ClearBoard:
11303         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11304             SendToICS(ics_prefix);
11305             SendToICS("bsetup clear\n");
11306         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11307             SendToICS(ics_prefix);
11308             SendToICS("clearboard\n");
11309         } else {
11310             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11311                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11312                 for (y = 0; y < BOARD_HEIGHT; y++) {
11313                     if (gameMode == IcsExamining) {
11314                         if (boards[currentMove][y][x] != EmptySquare) {
11315                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11316                                     AAA + x, ONE + y);
11317                             SendToICS(buf);
11318                         }
11319                     } else {
11320                         boards[0][y][x] = p;
11321                     }
11322                 }
11323             }
11324         }
11325         if (gameMode == EditPosition) {
11326             DrawPosition(FALSE, boards[0]);
11327         }
11328         break;
11329
11330       case WhitePlay:
11331         SetWhiteToPlayEvent();
11332         break;
11333
11334       case BlackPlay:
11335         SetBlackToPlayEvent();
11336         break;
11337
11338       case EmptySquare:
11339         if (gameMode == IcsExamining) {
11340             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11341             SendToICS(buf);
11342         } else {
11343             boards[0][y][x] = EmptySquare;
11344             DrawPosition(FALSE, boards[0]);
11345         }
11346         break;
11347
11348       case PromotePiece:
11349         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11350            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11351             selection = (ChessSquare) (PROMOTED piece);
11352         } else if(piece == EmptySquare) selection = WhiteSilver;
11353         else selection = (ChessSquare)((int)piece - 1);
11354         goto defaultlabel;
11355
11356       case DemotePiece:
11357         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11358            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11359             selection = (ChessSquare) (DEMOTED piece);
11360         } else if(piece == EmptySquare) selection = BlackSilver;
11361         else selection = (ChessSquare)((int)piece + 1);       
11362         goto defaultlabel;
11363
11364       case WhiteQueen:
11365       case BlackQueen:
11366         if(gameInfo.variant == VariantShatranj ||
11367            gameInfo.variant == VariantXiangqi  ||
11368            gameInfo.variant == VariantCourier    )
11369             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11370         goto defaultlabel;
11371
11372       case WhiteKing:
11373       case BlackKing:
11374         if(gameInfo.variant == VariantXiangqi)
11375             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11376         if(gameInfo.variant == VariantKnightmate)
11377             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11378       default:
11379         defaultlabel:
11380         if (gameMode == IcsExamining) {
11381             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11382                     PieceToChar(selection), AAA + x, ONE + y);
11383             SendToICS(buf);
11384         } else {
11385             boards[0][y][x] = selection;
11386             DrawPosition(FALSE, boards[0]);
11387         }
11388         break;
11389     }
11390 }
11391
11392
11393 void
11394 DropMenuEvent(selection, x, y)
11395      ChessSquare selection;
11396      int x, y;
11397 {
11398     ChessMove moveType;
11399
11400     switch (gameMode) {
11401       case IcsPlayingWhite:
11402       case MachinePlaysBlack:
11403         if (!WhiteOnMove(currentMove)) {
11404             DisplayMoveError(_("It is Black's turn"));
11405             return;
11406         }
11407         moveType = WhiteDrop;
11408         break;
11409       case IcsPlayingBlack:
11410       case MachinePlaysWhite:
11411         if (WhiteOnMove(currentMove)) {
11412             DisplayMoveError(_("It is White's turn"));
11413             return;
11414         }
11415         moveType = BlackDrop;
11416         break;
11417       case EditGame:
11418         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11419         break;
11420       default:
11421         return;
11422     }
11423
11424     if (moveType == BlackDrop && selection < BlackPawn) {
11425       selection = (ChessSquare) ((int) selection
11426                                  + (int) BlackPawn - (int) WhitePawn);
11427     }
11428     if (boards[currentMove][y][x] != EmptySquare) {
11429         DisplayMoveError(_("That square is occupied"));
11430         return;
11431     }
11432
11433     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11434 }
11435
11436 void
11437 AcceptEvent()
11438 {
11439     /* Accept a pending offer of any kind from opponent */
11440     
11441     if (appData.icsActive) {
11442         SendToICS(ics_prefix);
11443         SendToICS("accept\n");
11444     } else if (cmailMsgLoaded) {
11445         if (currentMove == cmailOldMove &&
11446             commentList[cmailOldMove] != NULL &&
11447             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11448                    "Black offers a draw" : "White offers a draw")) {
11449             TruncateGame();
11450             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11451             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11452         } else {
11453             DisplayError(_("There is no pending offer on this move"), 0);
11454             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11455         }
11456     } else {
11457         /* Not used for offers from chess program */
11458     }
11459 }
11460
11461 void
11462 DeclineEvent()
11463 {
11464     /* Decline a pending offer of any kind from opponent */
11465     
11466     if (appData.icsActive) {
11467         SendToICS(ics_prefix);
11468         SendToICS("decline\n");
11469     } else if (cmailMsgLoaded) {
11470         if (currentMove == cmailOldMove &&
11471             commentList[cmailOldMove] != NULL &&
11472             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11473                    "Black offers a draw" : "White offers a draw")) {
11474 #ifdef NOTDEF
11475             AppendComment(cmailOldMove, "Draw declined");
11476             DisplayComment(cmailOldMove - 1, "Draw declined");
11477 #endif /*NOTDEF*/
11478         } else {
11479             DisplayError(_("There is no pending offer on this move"), 0);
11480         }
11481     } else {
11482         /* Not used for offers from chess program */
11483     }
11484 }
11485
11486 void
11487 RematchEvent()
11488 {
11489     /* Issue ICS rematch command */
11490     if (appData.icsActive) {
11491         SendToICS(ics_prefix);
11492         SendToICS("rematch\n");
11493     }
11494 }
11495
11496 void
11497 CallFlagEvent()
11498 {
11499     /* Call your opponent's flag (claim a win on time) */
11500     if (appData.icsActive) {
11501         SendToICS(ics_prefix);
11502         SendToICS("flag\n");
11503     } else {
11504         switch (gameMode) {
11505           default:
11506             return;
11507           case MachinePlaysWhite:
11508             if (whiteFlag) {
11509                 if (blackFlag)
11510                   GameEnds(GameIsDrawn, "Both players ran out of time",
11511                            GE_PLAYER);
11512                 else
11513                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11514             } else {
11515                 DisplayError(_("Your opponent is not out of time"), 0);
11516             }
11517             break;
11518           case MachinePlaysBlack:
11519             if (blackFlag) {
11520                 if (whiteFlag)
11521                   GameEnds(GameIsDrawn, "Both players ran out of time",
11522                            GE_PLAYER);
11523                 else
11524                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11525             } else {
11526                 DisplayError(_("Your opponent is not out of time"), 0);
11527             }
11528             break;
11529         }
11530     }
11531 }
11532
11533 void
11534 DrawEvent()
11535 {
11536     /* Offer draw or accept pending draw offer from opponent */
11537     
11538     if (appData.icsActive) {
11539         /* Note: tournament rules require draw offers to be
11540            made after you make your move but before you punch
11541            your clock.  Currently ICS doesn't let you do that;
11542            instead, you immediately punch your clock after making
11543            a move, but you can offer a draw at any time. */
11544         
11545         SendToICS(ics_prefix);
11546         SendToICS("draw\n");
11547     } else if (cmailMsgLoaded) {
11548         if (currentMove == cmailOldMove &&
11549             commentList[cmailOldMove] != NULL &&
11550             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11551                    "Black offers a draw" : "White offers a draw")) {
11552             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11553             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11554         } else if (currentMove == cmailOldMove + 1) {
11555             char *offer = WhiteOnMove(cmailOldMove) ?
11556               "White offers a draw" : "Black offers a draw";
11557             AppendComment(currentMove, offer);
11558             DisplayComment(currentMove - 1, offer);
11559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11560         } else {
11561             DisplayError(_("You must make your move before offering a draw"), 0);
11562             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11563         }
11564     } else if (first.offeredDraw) {
11565         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11566     } else {
11567         if (first.sendDrawOffers) {
11568             SendToProgram("draw\n", &first);
11569             userOfferedDraw = TRUE;
11570         }
11571     }
11572 }
11573
11574 void
11575 AdjournEvent()
11576 {
11577     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11578     
11579     if (appData.icsActive) {
11580         SendToICS(ics_prefix);
11581         SendToICS("adjourn\n");
11582     } else {
11583         /* Currently GNU Chess doesn't offer or accept Adjourns */
11584     }
11585 }
11586
11587
11588 void
11589 AbortEvent()
11590 {
11591     /* Offer Abort or accept pending Abort offer from opponent */
11592     
11593     if (appData.icsActive) {
11594         SendToICS(ics_prefix);
11595         SendToICS("abort\n");
11596     } else {
11597         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11598     }
11599 }
11600
11601 void
11602 ResignEvent()
11603 {
11604     /* Resign.  You can do this even if it's not your turn. */
11605     
11606     if (appData.icsActive) {
11607         SendToICS(ics_prefix);
11608         SendToICS("resign\n");
11609     } else {
11610         switch (gameMode) {
11611           case MachinePlaysWhite:
11612             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11613             break;
11614           case MachinePlaysBlack:
11615             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11616             break;
11617           case EditGame:
11618             if (cmailMsgLoaded) {
11619                 TruncateGame();
11620                 if (WhiteOnMove(cmailOldMove)) {
11621                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11622                 } else {
11623                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11624                 }
11625                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11626             }
11627             break;
11628           default:
11629             break;
11630         }
11631     }
11632 }
11633
11634
11635 void
11636 StopObservingEvent()
11637 {
11638     /* Stop observing current games */
11639     SendToICS(ics_prefix);
11640     SendToICS("unobserve\n");
11641 }
11642
11643 void
11644 StopExaminingEvent()
11645 {
11646     /* Stop observing current game */
11647     SendToICS(ics_prefix);
11648     SendToICS("unexamine\n");
11649 }
11650
11651 void
11652 ForwardInner(target)
11653      int target;
11654 {
11655     int limit;
11656
11657     if (appData.debugMode)
11658         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11659                 target, currentMove, forwardMostMove);
11660
11661     if (gameMode == EditPosition)
11662       return;
11663
11664     if (gameMode == PlayFromGameFile && !pausing)
11665       PauseEvent();
11666     
11667     if (gameMode == IcsExamining && pausing)
11668       limit = pauseExamForwardMostMove;
11669     else
11670       limit = forwardMostMove;
11671     
11672     if (target > limit) target = limit;
11673
11674     if (target > 0 && moveList[target - 1][0]) {
11675         int fromX, fromY, toX, toY;
11676         toX = moveList[target - 1][2] - AAA;
11677         toY = moveList[target - 1][3] - ONE;
11678         if (moveList[target - 1][1] == '@') {
11679             if (appData.highlightLastMove) {
11680                 SetHighlights(-1, -1, toX, toY);
11681             }
11682         } else {
11683             fromX = moveList[target - 1][0] - AAA;
11684             fromY = moveList[target - 1][1] - ONE;
11685             if (target == currentMove + 1) {
11686                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11687             }
11688             if (appData.highlightLastMove) {
11689                 SetHighlights(fromX, fromY, toX, toY);
11690             }
11691         }
11692     }
11693     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11694         gameMode == Training || gameMode == PlayFromGameFile || 
11695         gameMode == AnalyzeFile) {
11696         while (currentMove < target) {
11697             SendMoveToProgram(currentMove++, &first);
11698         }
11699     } else {
11700         currentMove = target;
11701     }
11702     
11703     if (gameMode == EditGame || gameMode == EndOfGame) {
11704         whiteTimeRemaining = timeRemaining[0][currentMove];
11705         blackTimeRemaining = timeRemaining[1][currentMove];
11706     }
11707     DisplayBothClocks();
11708     DisplayMove(currentMove - 1);
11709     DrawPosition(FALSE, boards[currentMove]);
11710     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11711     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11712         DisplayComment(currentMove - 1, commentList[currentMove]);
11713     }
11714 }
11715
11716
11717 void
11718 ForwardEvent()
11719 {
11720     if (gameMode == IcsExamining && !pausing) {
11721         SendToICS(ics_prefix);
11722         SendToICS("forward\n");
11723     } else {
11724         ForwardInner(currentMove + 1);
11725     }
11726 }
11727
11728 void
11729 ToEndEvent()
11730 {
11731     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11732         /* to optimze, we temporarily turn off analysis mode while we feed
11733          * the remaining moves to the engine. Otherwise we get analysis output
11734          * after each move.
11735          */ 
11736         if (first.analysisSupport) {
11737           SendToProgram("exit\nforce\n", &first);
11738           first.analyzing = FALSE;
11739         }
11740     }
11741         
11742     if (gameMode == IcsExamining && !pausing) {
11743         SendToICS(ics_prefix);
11744         SendToICS("forward 999999\n");
11745     } else {
11746         ForwardInner(forwardMostMove);
11747     }
11748
11749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11750         /* we have fed all the moves, so reactivate analysis mode */
11751         SendToProgram("analyze\n", &first);
11752         first.analyzing = TRUE;
11753         /*first.maybeThinking = TRUE;*/
11754         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11755     }
11756 }
11757
11758 void
11759 BackwardInner(target)
11760      int target;
11761 {
11762     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11763
11764     if (appData.debugMode)
11765         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11766                 target, currentMove, forwardMostMove);
11767
11768     if (gameMode == EditPosition) return;
11769     if (currentMove <= backwardMostMove) {
11770         ClearHighlights();
11771         DrawPosition(full_redraw, boards[currentMove]);
11772         return;
11773     }
11774     if (gameMode == PlayFromGameFile && !pausing)
11775       PauseEvent();
11776     
11777     if (moveList[target][0]) {
11778         int fromX, fromY, toX, toY;
11779         toX = moveList[target][2] - AAA;
11780         toY = moveList[target][3] - ONE;
11781         if (moveList[target][1] == '@') {
11782             if (appData.highlightLastMove) {
11783                 SetHighlights(-1, -1, toX, toY);
11784             }
11785         } else {
11786             fromX = moveList[target][0] - AAA;
11787             fromY = moveList[target][1] - ONE;
11788             if (target == currentMove - 1) {
11789                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11790             }
11791             if (appData.highlightLastMove) {
11792                 SetHighlights(fromX, fromY, toX, toY);
11793             }
11794         }
11795     }
11796     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11797         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11798         while (currentMove > target) {
11799             SendToProgram("undo\n", &first);
11800             currentMove--;
11801         }
11802     } else {
11803         currentMove = target;
11804     }
11805     
11806     if (gameMode == EditGame || gameMode == EndOfGame) {
11807         whiteTimeRemaining = timeRemaining[0][currentMove];
11808         blackTimeRemaining = timeRemaining[1][currentMove];
11809     }
11810     DisplayBothClocks();
11811     DisplayMove(currentMove - 1);
11812     DrawPosition(full_redraw, boards[currentMove]);
11813     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11814     // [HGM] PV info: routine tests if comment empty
11815     DisplayComment(currentMove - 1, commentList[currentMove]);
11816 }
11817
11818 void
11819 BackwardEvent()
11820 {
11821     if (gameMode == IcsExamining && !pausing) {
11822         SendToICS(ics_prefix);
11823         SendToICS("backward\n");
11824     } else {
11825         BackwardInner(currentMove - 1);
11826     }
11827 }
11828
11829 void
11830 ToStartEvent()
11831 {
11832     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11833         /* to optimize, we temporarily turn off analysis mode while we undo
11834          * all the moves. Otherwise we get analysis output after each undo.
11835          */ 
11836         if (first.analysisSupport) {
11837           SendToProgram("exit\nforce\n", &first);
11838           first.analyzing = FALSE;
11839         }
11840     }
11841
11842     if (gameMode == IcsExamining && !pausing) {
11843         SendToICS(ics_prefix);
11844         SendToICS("backward 999999\n");
11845     } else {
11846         BackwardInner(backwardMostMove);
11847     }
11848
11849     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11850         /* we have fed all the moves, so reactivate analysis mode */
11851         SendToProgram("analyze\n", &first);
11852         first.analyzing = TRUE;
11853         /*first.maybeThinking = TRUE;*/
11854         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11855     }
11856 }
11857
11858 void
11859 ToNrEvent(int to)
11860 {
11861   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11862   if (to >= forwardMostMove) to = forwardMostMove;
11863   if (to <= backwardMostMove) to = backwardMostMove;
11864   if (to < currentMove) {
11865     BackwardInner(to);
11866   } else {
11867     ForwardInner(to);
11868   }
11869 }
11870
11871 void
11872 RevertEvent()
11873 {
11874     if (gameMode != IcsExamining) {
11875         DisplayError(_("You are not examining a game"), 0);
11876         return;
11877     }
11878     if (pausing) {
11879         DisplayError(_("You can't revert while pausing"), 0);
11880         return;
11881     }
11882     SendToICS(ics_prefix);
11883     SendToICS("revert\n");
11884 }
11885
11886 void
11887 RetractMoveEvent()
11888 {
11889     switch (gameMode) {
11890       case MachinePlaysWhite:
11891       case MachinePlaysBlack:
11892         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11893             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11894             return;
11895         }
11896         if (forwardMostMove < 2) return;
11897         currentMove = forwardMostMove = forwardMostMove - 2;
11898         whiteTimeRemaining = timeRemaining[0][currentMove];
11899         blackTimeRemaining = timeRemaining[1][currentMove];
11900         DisplayBothClocks();
11901         DisplayMove(currentMove - 1);
11902         ClearHighlights();/*!! could figure this out*/
11903         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11904         SendToProgram("remove\n", &first);
11905         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11906         break;
11907
11908       case BeginningOfGame:
11909       default:
11910         break;
11911
11912       case IcsPlayingWhite:
11913       case IcsPlayingBlack:
11914         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11915             SendToICS(ics_prefix);
11916             SendToICS("takeback 2\n");
11917         } else {
11918             SendToICS(ics_prefix);
11919             SendToICS("takeback 1\n");
11920         }
11921         break;
11922     }
11923 }
11924
11925 void
11926 MoveNowEvent()
11927 {
11928     ChessProgramState *cps;
11929
11930     switch (gameMode) {
11931       case MachinePlaysWhite:
11932         if (!WhiteOnMove(forwardMostMove)) {
11933             DisplayError(_("It is your turn"), 0);
11934             return;
11935         }
11936         cps = &first;
11937         break;
11938       case MachinePlaysBlack:
11939         if (WhiteOnMove(forwardMostMove)) {
11940             DisplayError(_("It is your turn"), 0);
11941             return;
11942         }
11943         cps = &first;
11944         break;
11945       case TwoMachinesPlay:
11946         if (WhiteOnMove(forwardMostMove) ==
11947             (first.twoMachinesColor[0] == 'w')) {
11948             cps = &first;
11949         } else {
11950             cps = &second;
11951         }
11952         break;
11953       case BeginningOfGame:
11954       default:
11955         return;
11956     }
11957     SendToProgram("?\n", cps);
11958 }
11959
11960 void
11961 TruncateGameEvent()
11962 {
11963     EditGameEvent();
11964     if (gameMode != EditGame) return;
11965     TruncateGame();
11966 }
11967
11968 void
11969 TruncateGame()
11970 {
11971     if (forwardMostMove > currentMove) {
11972         if (gameInfo.resultDetails != NULL) {
11973             free(gameInfo.resultDetails);
11974             gameInfo.resultDetails = NULL;
11975             gameInfo.result = GameUnfinished;
11976         }
11977         forwardMostMove = currentMove;
11978         HistorySet(parseList, backwardMostMove, forwardMostMove,
11979                    currentMove-1);
11980     }
11981 }
11982
11983 void
11984 HintEvent()
11985 {
11986     if (appData.noChessProgram) return;
11987     switch (gameMode) {
11988       case MachinePlaysWhite:
11989         if (WhiteOnMove(forwardMostMove)) {
11990             DisplayError(_("Wait until your turn"), 0);
11991             return;
11992         }
11993         break;
11994       case BeginningOfGame:
11995       case MachinePlaysBlack:
11996         if (!WhiteOnMove(forwardMostMove)) {
11997             DisplayError(_("Wait until your turn"), 0);
11998             return;
11999         }
12000         break;
12001       default:
12002         DisplayError(_("No hint available"), 0);
12003         return;
12004     }
12005     SendToProgram("hint\n", &first);
12006     hintRequested = TRUE;
12007 }
12008
12009 void
12010 BookEvent()
12011 {
12012     if (appData.noChessProgram) return;
12013     switch (gameMode) {
12014       case MachinePlaysWhite:
12015         if (WhiteOnMove(forwardMostMove)) {
12016             DisplayError(_("Wait until your turn"), 0);
12017             return;
12018         }
12019         break;
12020       case BeginningOfGame:
12021       case MachinePlaysBlack:
12022         if (!WhiteOnMove(forwardMostMove)) {
12023             DisplayError(_("Wait until your turn"), 0);
12024             return;
12025         }
12026         break;
12027       case EditPosition:
12028         EditPositionDone(TRUE);
12029         break;
12030       case TwoMachinesPlay:
12031         return;
12032       default:
12033         break;
12034     }
12035     SendToProgram("bk\n", &first);
12036     bookOutput[0] = NULLCHAR;
12037     bookRequested = TRUE;
12038 }
12039
12040 void
12041 AboutGameEvent()
12042 {
12043     char *tags = PGNTags(&gameInfo);
12044     TagsPopUp(tags, CmailMsg());
12045     free(tags);
12046 }
12047
12048 /* end button procedures */
12049
12050 void
12051 PrintPosition(fp, move)
12052      FILE *fp;
12053      int move;
12054 {
12055     int i, j;
12056     
12057     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12058         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12059             char c = PieceToChar(boards[move][i][j]);
12060             fputc(c == 'x' ? '.' : c, fp);
12061             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12062         }
12063     }
12064     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12065       fprintf(fp, "white to play\n");
12066     else
12067       fprintf(fp, "black to play\n");
12068 }
12069
12070 void
12071 PrintOpponents(fp)
12072      FILE *fp;
12073 {
12074     if (gameInfo.white != NULL) {
12075         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12076     } else {
12077         fprintf(fp, "\n");
12078     }
12079 }
12080
12081 /* Find last component of program's own name, using some heuristics */
12082 void
12083 TidyProgramName(prog, host, buf)
12084      char *prog, *host, buf[MSG_SIZ];
12085 {
12086     char *p, *q;
12087     int local = (strcmp(host, "localhost") == 0);
12088     while (!local && (p = strchr(prog, ';')) != NULL) {
12089         p++;
12090         while (*p == ' ') p++;
12091         prog = p;
12092     }
12093     if (*prog == '"' || *prog == '\'') {
12094         q = strchr(prog + 1, *prog);
12095     } else {
12096         q = strchr(prog, ' ');
12097     }
12098     if (q == NULL) q = prog + strlen(prog);
12099     p = q;
12100     while (p >= prog && *p != '/' && *p != '\\') p--;
12101     p++;
12102     if(p == prog && *p == '"') p++;
12103     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12104     memcpy(buf, p, q - p);
12105     buf[q - p] = NULLCHAR;
12106     if (!local) {
12107         strcat(buf, "@");
12108         strcat(buf, host);
12109     }
12110 }
12111
12112 char *
12113 TimeControlTagValue()
12114 {
12115     char buf[MSG_SIZ];
12116     if (!appData.clockMode) {
12117         strcpy(buf, "-");
12118     } else if (movesPerSession > 0) {
12119         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12120     } else if (timeIncrement == 0) {
12121         sprintf(buf, "%ld", timeControl/1000);
12122     } else {
12123         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12124     }
12125     return StrSave(buf);
12126 }
12127
12128 void
12129 SetGameInfo()
12130 {
12131     /* This routine is used only for certain modes */
12132     VariantClass v = gameInfo.variant;
12133     ClearGameInfo(&gameInfo);
12134     gameInfo.variant = v;
12135
12136     switch (gameMode) {
12137       case MachinePlaysWhite:
12138         gameInfo.event = StrSave( appData.pgnEventHeader );
12139         gameInfo.site = StrSave(HostName());
12140         gameInfo.date = PGNDate();
12141         gameInfo.round = StrSave("-");
12142         gameInfo.white = StrSave(first.tidy);
12143         gameInfo.black = StrSave(UserName());
12144         gameInfo.timeControl = TimeControlTagValue();
12145         break;
12146
12147       case MachinePlaysBlack:
12148         gameInfo.event = StrSave( appData.pgnEventHeader );
12149         gameInfo.site = StrSave(HostName());
12150         gameInfo.date = PGNDate();
12151         gameInfo.round = StrSave("-");
12152         gameInfo.white = StrSave(UserName());
12153         gameInfo.black = StrSave(first.tidy);
12154         gameInfo.timeControl = TimeControlTagValue();
12155         break;
12156
12157       case TwoMachinesPlay:
12158         gameInfo.event = StrSave( appData.pgnEventHeader );
12159         gameInfo.site = StrSave(HostName());
12160         gameInfo.date = PGNDate();
12161         if (matchGame > 0) {
12162             char buf[MSG_SIZ];
12163             sprintf(buf, "%d", matchGame);
12164             gameInfo.round = StrSave(buf);
12165         } else {
12166             gameInfo.round = StrSave("-");
12167         }
12168         if (first.twoMachinesColor[0] == 'w') {
12169             gameInfo.white = StrSave(first.tidy);
12170             gameInfo.black = StrSave(second.tidy);
12171         } else {
12172             gameInfo.white = StrSave(second.tidy);
12173             gameInfo.black = StrSave(first.tidy);
12174         }
12175         gameInfo.timeControl = TimeControlTagValue();
12176         break;
12177
12178       case EditGame:
12179         gameInfo.event = StrSave("Edited game");
12180         gameInfo.site = StrSave(HostName());
12181         gameInfo.date = PGNDate();
12182         gameInfo.round = StrSave("-");
12183         gameInfo.white = StrSave("-");
12184         gameInfo.black = StrSave("-");
12185         break;
12186
12187       case EditPosition:
12188         gameInfo.event = StrSave("Edited position");
12189         gameInfo.site = StrSave(HostName());
12190         gameInfo.date = PGNDate();
12191         gameInfo.round = StrSave("-");
12192         gameInfo.white = StrSave("-");
12193         gameInfo.black = StrSave("-");
12194         break;
12195
12196       case IcsPlayingWhite:
12197       case IcsPlayingBlack:
12198       case IcsObserving:
12199       case IcsExamining:
12200         break;
12201
12202       case PlayFromGameFile:
12203         gameInfo.event = StrSave("Game from non-PGN file");
12204         gameInfo.site = StrSave(HostName());
12205         gameInfo.date = PGNDate();
12206         gameInfo.round = StrSave("-");
12207         gameInfo.white = StrSave("?");
12208         gameInfo.black = StrSave("?");
12209         break;
12210
12211       default:
12212         break;
12213     }
12214 }
12215
12216 void
12217 ReplaceComment(index, text)
12218      int index;
12219      char *text;
12220 {
12221     int len;
12222
12223     while (*text == '\n') text++;
12224     len = strlen(text);
12225     while (len > 0 && text[len - 1] == '\n') len--;
12226
12227     if (commentList[index] != NULL)
12228       free(commentList[index]);
12229
12230     if (len == 0) {
12231         commentList[index] = NULL;
12232         return;
12233     }
12234     commentList[index] = (char *) malloc(len + 2);
12235     strncpy(commentList[index], text, len);
12236     commentList[index][len] = '\n';
12237     commentList[index][len + 1] = NULLCHAR;
12238 }
12239
12240 void
12241 CrushCRs(text)
12242      char *text;
12243 {
12244   char *p = text;
12245   char *q = text;
12246   char ch;
12247
12248   do {
12249     ch = *p++;
12250     if (ch == '\r') continue;
12251     *q++ = ch;
12252   } while (ch != '\0');
12253 }
12254
12255 void
12256 AppendComment(index, text)
12257      int index;
12258      char *text;
12259 {
12260     int oldlen, len;
12261     char *old;
12262
12263     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12264
12265     CrushCRs(text);
12266     while (*text == '\n') text++;
12267     len = strlen(text);
12268     while (len > 0 && text[len - 1] == '\n') len--;
12269
12270     if (len == 0) return;
12271
12272     if (commentList[index] != NULL) {
12273         old = commentList[index];
12274         oldlen = strlen(old);
12275         commentList[index] = (char *) malloc(oldlen + len + 2);
12276         strcpy(commentList[index], old);
12277         free(old);
12278         strncpy(&commentList[index][oldlen], text, len);
12279         commentList[index][oldlen + len] = '\n';
12280         commentList[index][oldlen + len + 1] = NULLCHAR;
12281     } else {
12282         commentList[index] = (char *) malloc(len + 2);
12283         strncpy(commentList[index], text, len);
12284         commentList[index][len] = '\n';
12285         commentList[index][len + 1] = NULLCHAR;
12286     }
12287 }
12288
12289 static char * FindStr( char * text, char * sub_text )
12290 {
12291     char * result = strstr( text, sub_text );
12292
12293     if( result != NULL ) {
12294         result += strlen( sub_text );
12295     }
12296
12297     return result;
12298 }
12299
12300 /* [AS] Try to extract PV info from PGN comment */
12301 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12302 char *GetInfoFromComment( int index, char * text )
12303 {
12304     char * sep = text;
12305
12306     if( text != NULL && index > 0 ) {
12307         int score = 0;
12308         int depth = 0;
12309         int time = -1, sec = 0, deci;
12310         char * s_eval = FindStr( text, "[%eval " );
12311         char * s_emt = FindStr( text, "[%emt " );
12312
12313         if( s_eval != NULL || s_emt != NULL ) {
12314             /* New style */
12315             char delim;
12316
12317             if( s_eval != NULL ) {
12318                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12319                     return text;
12320                 }
12321
12322                 if( delim != ']' ) {
12323                     return text;
12324                 }
12325             }
12326
12327             if( s_emt != NULL ) {
12328             }
12329         }
12330         else {
12331             /* We expect something like: [+|-]nnn.nn/dd */
12332             int score_lo = 0;
12333
12334             sep = strchr( text, '/' );
12335             if( sep == NULL || sep < (text+4) ) {
12336                 return text;
12337             }
12338
12339             time = -1; sec = -1; deci = -1;
12340             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12341                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12342                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12343                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12344                 return text;
12345             }
12346
12347             if( score_lo < 0 || score_lo >= 100 ) {
12348                 return text;
12349             }
12350
12351             if(sec >= 0) time = 600*time + 10*sec; else
12352             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12353
12354             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12355
12356             /* [HGM] PV time: now locate end of PV info */
12357             while( *++sep >= '0' && *sep <= '9'); // strip depth
12358             if(time >= 0)
12359             while( *++sep >= '0' && *sep <= '9'); // strip time
12360             if(sec >= 0)
12361             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12362             if(deci >= 0)
12363             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12364             while(*sep == ' ') sep++;
12365         }
12366
12367         if( depth <= 0 ) {
12368             return text;
12369         }
12370
12371         if( time < 0 ) {
12372             time = -1;
12373         }
12374
12375         pvInfoList[index-1].depth = depth;
12376         pvInfoList[index-1].score = score;
12377         pvInfoList[index-1].time  = 10*time; // centi-sec
12378     }
12379     return sep;
12380 }
12381
12382 void
12383 SendToProgram(message, cps)
12384      char *message;
12385      ChessProgramState *cps;
12386 {
12387     int count, outCount, error;
12388     char buf[MSG_SIZ];
12389
12390     if (cps->pr == NULL) return;
12391     Attention(cps);
12392     
12393     if (appData.debugMode) {
12394         TimeMark now;
12395         GetTimeMark(&now);
12396         fprintf(debugFP, "%ld >%-6s: %s", 
12397                 SubtractTimeMarks(&now, &programStartTime),
12398                 cps->which, message);
12399     }
12400     
12401     count = strlen(message);
12402     outCount = OutputToProcess(cps->pr, message, count, &error);
12403     if (outCount < count && !exiting 
12404                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12405         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12406         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12407             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12408                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12409                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12410             } else {
12411                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12412             }
12413             gameInfo.resultDetails = StrSave(buf);
12414         }
12415         DisplayFatalError(buf, error, 1);
12416     }
12417 }
12418
12419 void
12420 ReceiveFromProgram(isr, closure, message, count, error)
12421      InputSourceRef isr;
12422      VOIDSTAR closure;
12423      char *message;
12424      int count;
12425      int error;
12426 {
12427     char *end_str;
12428     char buf[MSG_SIZ];
12429     ChessProgramState *cps = (ChessProgramState *)closure;
12430
12431     if (isr != cps->isr) return; /* Killed intentionally */
12432     if (count <= 0) {
12433         if (count == 0) {
12434             sprintf(buf,
12435                     _("Error: %s chess program (%s) exited unexpectedly"),
12436                     cps->which, cps->program);
12437         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12438                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12439                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12440                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12441                 } else {
12442                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12443                 }
12444                 gameInfo.resultDetails = StrSave(buf);
12445             }
12446             RemoveInputSource(cps->isr);
12447             DisplayFatalError(buf, 0, 1);
12448         } else {
12449             sprintf(buf,
12450                     _("Error reading from %s chess program (%s)"),
12451                     cps->which, cps->program);
12452             RemoveInputSource(cps->isr);
12453
12454             /* [AS] Program is misbehaving badly... kill it */
12455             if( count == -2 ) {
12456                 DestroyChildProcess( cps->pr, 9 );
12457                 cps->pr = NoProc;
12458             }
12459
12460             DisplayFatalError(buf, error, 1);
12461         }
12462         return;
12463     }
12464     
12465     if ((end_str = strchr(message, '\r')) != NULL)
12466       *end_str = NULLCHAR;
12467     if ((end_str = strchr(message, '\n')) != NULL)
12468       *end_str = NULLCHAR;
12469     
12470     if (appData.debugMode) {
12471         TimeMark now; int print = 1;
12472         char *quote = ""; char c; int i;
12473
12474         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12475                 char start = message[0];
12476                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12477                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12478                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12479                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12480                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12481                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12482                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12483                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12484                         { quote = "# "; print = (appData.engineComments == 2); }
12485                 message[0] = start; // restore original message
12486         }
12487         if(print) {
12488                 GetTimeMark(&now);
12489                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12490                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12491                         quote,
12492                         message);
12493         }
12494     }
12495
12496     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12497     if (appData.icsEngineAnalyze) {
12498         if (strstr(message, "whisper") != NULL ||
12499              strstr(message, "kibitz") != NULL || 
12500             strstr(message, "tellics") != NULL) return;
12501     }
12502
12503     HandleMachineMove(message, cps);
12504 }
12505
12506
12507 void
12508 SendTimeControl(cps, mps, tc, inc, sd, st)
12509      ChessProgramState *cps;
12510      int mps, inc, sd, st;
12511      long tc;
12512 {
12513     char buf[MSG_SIZ];
12514     int seconds;
12515
12516     if( timeControl_2 > 0 ) {
12517         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12518             tc = timeControl_2;
12519         }
12520     }
12521     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12522     inc /= cps->timeOdds;
12523     st  /= cps->timeOdds;
12524
12525     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12526
12527     if (st > 0) {
12528       /* Set exact time per move, normally using st command */
12529       if (cps->stKludge) {
12530         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12531         seconds = st % 60;
12532         if (seconds == 0) {
12533           sprintf(buf, "level 1 %d\n", st/60);
12534         } else {
12535           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12536         }
12537       } else {
12538         sprintf(buf, "st %d\n", st);
12539       }
12540     } else {
12541       /* Set conventional or incremental time control, using level command */
12542       if (seconds == 0) {
12543         /* Note old gnuchess bug -- minutes:seconds used to not work.
12544            Fixed in later versions, but still avoid :seconds
12545            when seconds is 0. */
12546         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12547       } else {
12548         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12549                 seconds, inc/1000);
12550       }
12551     }
12552     SendToProgram(buf, cps);
12553
12554     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12555     /* Orthogonally, limit search to given depth */
12556     if (sd > 0) {
12557       if (cps->sdKludge) {
12558         sprintf(buf, "depth\n%d\n", sd);
12559       } else {
12560         sprintf(buf, "sd %d\n", sd);
12561       }
12562       SendToProgram(buf, cps);
12563     }
12564
12565     if(cps->nps > 0) { /* [HGM] nps */
12566         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12567         else {
12568                 sprintf(buf, "nps %d\n", cps->nps);
12569               SendToProgram(buf, cps);
12570         }
12571     }
12572 }
12573
12574 ChessProgramState *WhitePlayer()
12575 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12576 {
12577     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12578        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12579         return &second;
12580     return &first;
12581 }
12582
12583 void
12584 SendTimeRemaining(cps, machineWhite)
12585      ChessProgramState *cps;
12586      int /*boolean*/ machineWhite;
12587 {
12588     char message[MSG_SIZ];
12589     long time, otime;
12590
12591     /* Note: this routine must be called when the clocks are stopped
12592        or when they have *just* been set or switched; otherwise
12593        it will be off by the time since the current tick started.
12594     */
12595     if (machineWhite) {
12596         time = whiteTimeRemaining / 10;
12597         otime = blackTimeRemaining / 10;
12598     } else {
12599         time = blackTimeRemaining / 10;
12600         otime = whiteTimeRemaining / 10;
12601     }
12602     /* [HGM] translate opponent's time by time-odds factor */
12603     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12604     if (appData.debugMode) {
12605         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12606     }
12607
12608     if (time <= 0) time = 1;
12609     if (otime <= 0) otime = 1;
12610     
12611     sprintf(message, "time %ld\n", time);
12612     SendToProgram(message, cps);
12613
12614     sprintf(message, "otim %ld\n", otime);
12615     SendToProgram(message, cps);
12616 }
12617
12618 int
12619 BoolFeature(p, name, loc, cps)
12620      char **p;
12621      char *name;
12622      int *loc;
12623      ChessProgramState *cps;
12624 {
12625   char buf[MSG_SIZ];
12626   int len = strlen(name);
12627   int val;
12628   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12629     (*p) += len + 1;
12630     sscanf(*p, "%d", &val);
12631     *loc = (val != 0);
12632     while (**p && **p != ' ') (*p)++;
12633     sprintf(buf, "accepted %s\n", name);
12634     SendToProgram(buf, cps);
12635     return TRUE;
12636   }
12637   return FALSE;
12638 }
12639
12640 int
12641 IntFeature(p, name, loc, cps)
12642      char **p;
12643      char *name;
12644      int *loc;
12645      ChessProgramState *cps;
12646 {
12647   char buf[MSG_SIZ];
12648   int len = strlen(name);
12649   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12650     (*p) += len + 1;
12651     sscanf(*p, "%d", loc);
12652     while (**p && **p != ' ') (*p)++;
12653     sprintf(buf, "accepted %s\n", name);
12654     SendToProgram(buf, cps);
12655     return TRUE;
12656   }
12657   return FALSE;
12658 }
12659
12660 int
12661 StringFeature(p, name, loc, cps)
12662      char **p;
12663      char *name;
12664      char loc[];
12665      ChessProgramState *cps;
12666 {
12667   char buf[MSG_SIZ];
12668   int len = strlen(name);
12669   if (strncmp((*p), name, len) == 0
12670       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12671     (*p) += len + 2;
12672     sscanf(*p, "%[^\"]", loc);
12673     while (**p && **p != '\"') (*p)++;
12674     if (**p == '\"') (*p)++;
12675     sprintf(buf, "accepted %s\n", name);
12676     SendToProgram(buf, cps);
12677     return TRUE;
12678   }
12679   return FALSE;
12680 }
12681
12682 int 
12683 ParseOption(Option *opt, ChessProgramState *cps)
12684 // [HGM] options: process the string that defines an engine option, and determine
12685 // name, type, default value, and allowed value range
12686 {
12687         char *p, *q, buf[MSG_SIZ];
12688         int n, min = (-1)<<31, max = 1<<31, def;
12689
12690         if(p = strstr(opt->name, " -spin ")) {
12691             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12692             if(max < min) max = min; // enforce consistency
12693             if(def < min) def = min;
12694             if(def > max) def = max;
12695             opt->value = def;
12696             opt->min = min;
12697             opt->max = max;
12698             opt->type = Spin;
12699         } else if((p = strstr(opt->name, " -slider "))) {
12700             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12701             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12702             if(max < min) max = min; // enforce consistency
12703             if(def < min) def = min;
12704             if(def > max) def = max;
12705             opt->value = def;
12706             opt->min = min;
12707             opt->max = max;
12708             opt->type = Spin; // Slider;
12709         } else if((p = strstr(opt->name, " -string "))) {
12710             opt->textValue = p+9;
12711             opt->type = TextBox;
12712         } else if((p = strstr(opt->name, " -file "))) {
12713             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12714             opt->textValue = p+7;
12715             opt->type = TextBox; // FileName;
12716         } else if((p = strstr(opt->name, " -path "))) {
12717             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12718             opt->textValue = p+7;
12719             opt->type = TextBox; // PathName;
12720         } else if(p = strstr(opt->name, " -check ")) {
12721             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12722             opt->value = (def != 0);
12723             opt->type = CheckBox;
12724         } else if(p = strstr(opt->name, " -combo ")) {
12725             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12726             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12727             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12728             opt->value = n = 0;
12729             while(q = StrStr(q, " /// ")) {
12730                 n++; *q = 0;    // count choices, and null-terminate each of them
12731                 q += 5;
12732                 if(*q == '*') { // remember default, which is marked with * prefix
12733                     q++;
12734                     opt->value = n;
12735                 }
12736                 cps->comboList[cps->comboCnt++] = q;
12737             }
12738             cps->comboList[cps->comboCnt++] = NULL;
12739             opt->max = n + 1;
12740             opt->type = ComboBox;
12741         } else if(p = strstr(opt->name, " -button")) {
12742             opt->type = Button;
12743         } else if(p = strstr(opt->name, " -save")) {
12744             opt->type = SaveButton;
12745         } else return FALSE;
12746         *p = 0; // terminate option name
12747         // now look if the command-line options define a setting for this engine option.
12748         if(cps->optionSettings && cps->optionSettings[0])
12749             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12750         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12751                 sprintf(buf, "option %s", p);
12752                 if(p = strstr(buf, ",")) *p = 0;
12753                 strcat(buf, "\n");
12754                 SendToProgram(buf, cps);
12755         }
12756         return TRUE;
12757 }
12758
12759 void
12760 FeatureDone(cps, val)
12761      ChessProgramState* cps;
12762      int val;
12763 {
12764   DelayedEventCallback cb = GetDelayedEvent();
12765   if ((cb == InitBackEnd3 && cps == &first) ||
12766       (cb == TwoMachinesEventIfReady && cps == &second)) {
12767     CancelDelayedEvent();
12768     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12769   }
12770   cps->initDone = val;
12771 }
12772
12773 /* Parse feature command from engine */
12774 void
12775 ParseFeatures(args, cps)
12776      char* args;
12777      ChessProgramState *cps;  
12778 {
12779   char *p = args;
12780   char *q;
12781   int val;
12782   char buf[MSG_SIZ];
12783
12784   for (;;) {
12785     while (*p == ' ') p++;
12786     if (*p == NULLCHAR) return;
12787
12788     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12789     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12790     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12791     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12792     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12793     if (BoolFeature(&p, "reuse", &val, cps)) {
12794       /* Engine can disable reuse, but can't enable it if user said no */
12795       if (!val) cps->reuse = FALSE;
12796       continue;
12797     }
12798     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12799     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12800       if (gameMode == TwoMachinesPlay) {
12801         DisplayTwoMachinesTitle();
12802       } else {
12803         DisplayTitle("");
12804       }
12805       continue;
12806     }
12807     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12808     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12809     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12810     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12811     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12812     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12813     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12814     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12815     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12816     if (IntFeature(&p, "done", &val, cps)) {
12817       FeatureDone(cps, val);
12818       continue;
12819     }
12820     /* Added by Tord: */
12821     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12822     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12823     /* End of additions by Tord */
12824
12825     /* [HGM] added features: */
12826     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12827     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12828     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12829     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12830     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12831     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12832     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12833         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12834             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12835             SendToProgram(buf, cps);
12836             continue;
12837         }
12838         if(cps->nrOptions >= MAX_OPTIONS) {
12839             cps->nrOptions--;
12840             sprintf(buf, "%s engine has too many options\n", cps->which);
12841             DisplayError(buf, 0);
12842         }
12843         continue;
12844     }
12845     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12846     /* End of additions by HGM */
12847
12848     /* unknown feature: complain and skip */
12849     q = p;
12850     while (*q && *q != '=') q++;
12851     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12852     SendToProgram(buf, cps);
12853     p = q;
12854     if (*p == '=') {
12855       p++;
12856       if (*p == '\"') {
12857         p++;
12858         while (*p && *p != '\"') p++;
12859         if (*p == '\"') p++;
12860       } else {
12861         while (*p && *p != ' ') p++;
12862       }
12863     }
12864   }
12865
12866 }
12867
12868 void
12869 PeriodicUpdatesEvent(newState)
12870      int newState;
12871 {
12872     if (newState == appData.periodicUpdates)
12873       return;
12874
12875     appData.periodicUpdates=newState;
12876
12877     /* Display type changes, so update it now */
12878 //    DisplayAnalysis();
12879
12880     /* Get the ball rolling again... */
12881     if (newState) {
12882         AnalysisPeriodicEvent(1);
12883         StartAnalysisClock();
12884     }
12885 }
12886
12887 void
12888 PonderNextMoveEvent(newState)
12889      int newState;
12890 {
12891     if (newState == appData.ponderNextMove) return;
12892     if (gameMode == EditPosition) EditPositionDone(TRUE);
12893     if (newState) {
12894         SendToProgram("hard\n", &first);
12895         if (gameMode == TwoMachinesPlay) {
12896             SendToProgram("hard\n", &second);
12897         }
12898     } else {
12899         SendToProgram("easy\n", &first);
12900         thinkOutput[0] = NULLCHAR;
12901         if (gameMode == TwoMachinesPlay) {
12902             SendToProgram("easy\n", &second);
12903         }
12904     }
12905     appData.ponderNextMove = newState;
12906 }
12907
12908 void
12909 NewSettingEvent(option, command, value)
12910      char *command;
12911      int option, value;
12912 {
12913     char buf[MSG_SIZ];
12914
12915     if (gameMode == EditPosition) EditPositionDone(TRUE);
12916     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12917     SendToProgram(buf, &first);
12918     if (gameMode == TwoMachinesPlay) {
12919         SendToProgram(buf, &second);
12920     }
12921 }
12922
12923 void
12924 ShowThinkingEvent()
12925 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12926 {
12927     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12928     int newState = appData.showThinking
12929         // [HGM] thinking: other features now need thinking output as well
12930         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12931     
12932     if (oldState == newState) return;
12933     oldState = newState;
12934     if (gameMode == EditPosition) EditPositionDone(TRUE);
12935     if (oldState) {
12936         SendToProgram("post\n", &first);
12937         if (gameMode == TwoMachinesPlay) {
12938             SendToProgram("post\n", &second);
12939         }
12940     } else {
12941         SendToProgram("nopost\n", &first);
12942         thinkOutput[0] = NULLCHAR;
12943         if (gameMode == TwoMachinesPlay) {
12944             SendToProgram("nopost\n", &second);
12945         }
12946     }
12947 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12948 }
12949
12950 void
12951 AskQuestionEvent(title, question, replyPrefix, which)
12952      char *title; char *question; char *replyPrefix; char *which;
12953 {
12954   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12955   if (pr == NoProc) return;
12956   AskQuestion(title, question, replyPrefix, pr);
12957 }
12958
12959 void
12960 DisplayMove(moveNumber)
12961      int moveNumber;
12962 {
12963     char message[MSG_SIZ];
12964     char res[MSG_SIZ];
12965     char cpThinkOutput[MSG_SIZ];
12966
12967     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12968     
12969     if (moveNumber == forwardMostMove - 1 || 
12970         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12971
12972         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12973
12974         if (strchr(cpThinkOutput, '\n')) {
12975             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12976         }
12977     } else {
12978         *cpThinkOutput = NULLCHAR;
12979     }
12980
12981     /* [AS] Hide thinking from human user */
12982     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12983         *cpThinkOutput = NULLCHAR;
12984         if( thinkOutput[0] != NULLCHAR ) {
12985             int i;
12986
12987             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12988                 cpThinkOutput[i] = '.';
12989             }
12990             cpThinkOutput[i] = NULLCHAR;
12991             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12992         }
12993     }
12994
12995     if (moveNumber == forwardMostMove - 1 &&
12996         gameInfo.resultDetails != NULL) {
12997         if (gameInfo.resultDetails[0] == NULLCHAR) {
12998             sprintf(res, " %s", PGNResult(gameInfo.result));
12999         } else {
13000             sprintf(res, " {%s} %s",
13001                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13002         }
13003     } else {
13004         res[0] = NULLCHAR;
13005     }
13006
13007     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13008         DisplayMessage(res, cpThinkOutput);
13009     } else {
13010         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13011                 WhiteOnMove(moveNumber) ? " " : ".. ",
13012                 parseList[moveNumber], res);
13013         DisplayMessage(message, cpThinkOutput);
13014     }
13015 }
13016
13017 void
13018 DisplayComment(moveNumber, text)
13019      int moveNumber;
13020      char *text;
13021 {
13022     char title[MSG_SIZ];
13023     char buf[8000]; // comment can be long!
13024     int score, depth;
13025     
13026     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13027       strcpy(title, "Comment");
13028     } else {
13029       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13030               WhiteOnMove(moveNumber) ? " " : ".. ",
13031               parseList[moveNumber]);
13032     }
13033     // [HGM] PV info: display PV info together with (or as) comment
13034     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13035       if(text == NULL) text = "";                                           
13036       score = pvInfoList[moveNumber].score;
13037       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13038               depth, (pvInfoList[moveNumber].time+50)/100, text);
13039       text = buf;
13040     }
13041     if (text != NULL && (appData.autoDisplayComment || commentUp))
13042         CommentPopUp(title, text);
13043 }
13044
13045 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13046  * might be busy thinking or pondering.  It can be omitted if your
13047  * gnuchess is configured to stop thinking immediately on any user
13048  * input.  However, that gnuchess feature depends on the FIONREAD
13049  * ioctl, which does not work properly on some flavors of Unix.
13050  */
13051 void
13052 Attention(cps)
13053      ChessProgramState *cps;
13054 {
13055 #if ATTENTION
13056     if (!cps->useSigint) return;
13057     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13058     switch (gameMode) {
13059       case MachinePlaysWhite:
13060       case MachinePlaysBlack:
13061       case TwoMachinesPlay:
13062       case IcsPlayingWhite:
13063       case IcsPlayingBlack:
13064       case AnalyzeMode:
13065       case AnalyzeFile:
13066         /* Skip if we know it isn't thinking */
13067         if (!cps->maybeThinking) return;
13068         if (appData.debugMode)
13069           fprintf(debugFP, "Interrupting %s\n", cps->which);
13070         InterruptChildProcess(cps->pr);
13071         cps->maybeThinking = FALSE;
13072         break;
13073       default:
13074         break;
13075     }
13076 #endif /*ATTENTION*/
13077 }
13078
13079 int
13080 CheckFlags()
13081 {
13082     if (whiteTimeRemaining <= 0) {
13083         if (!whiteFlag) {
13084             whiteFlag = TRUE;
13085             if (appData.icsActive) {
13086                 if (appData.autoCallFlag &&
13087                     gameMode == IcsPlayingBlack && !blackFlag) {
13088                   SendToICS(ics_prefix);
13089                   SendToICS("flag\n");
13090                 }
13091             } else {
13092                 if (blackFlag) {
13093                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13094                 } else {
13095                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13096                     if (appData.autoCallFlag) {
13097                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13098                         return TRUE;
13099                     }
13100                 }
13101             }
13102         }
13103     }
13104     if (blackTimeRemaining <= 0) {
13105         if (!blackFlag) {
13106             blackFlag = TRUE;
13107             if (appData.icsActive) {
13108                 if (appData.autoCallFlag &&
13109                     gameMode == IcsPlayingWhite && !whiteFlag) {
13110                   SendToICS(ics_prefix);
13111                   SendToICS("flag\n");
13112                 }
13113             } else {
13114                 if (whiteFlag) {
13115                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13116                 } else {
13117                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13118                     if (appData.autoCallFlag) {
13119                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13120                         return TRUE;
13121                     }
13122                 }
13123             }
13124         }
13125     }
13126     return FALSE;
13127 }
13128
13129 void
13130 CheckTimeControl()
13131 {
13132     if (!appData.clockMode || appData.icsActive ||
13133         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13134
13135     /*
13136      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13137      */
13138     if ( !WhiteOnMove(forwardMostMove) )
13139         /* White made time control */
13140         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13141         /* [HGM] time odds: correct new time quota for time odds! */
13142                                             / WhitePlayer()->timeOdds;
13143       else
13144         /* Black made time control */
13145         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13146                                             / WhitePlayer()->other->timeOdds;
13147 }
13148
13149 void
13150 DisplayBothClocks()
13151 {
13152     int wom = gameMode == EditPosition ?
13153       !blackPlaysFirst : WhiteOnMove(currentMove);
13154     DisplayWhiteClock(whiteTimeRemaining, wom);
13155     DisplayBlackClock(blackTimeRemaining, !wom);
13156 }
13157
13158
13159 /* Timekeeping seems to be a portability nightmare.  I think everyone
13160    has ftime(), but I'm really not sure, so I'm including some ifdefs
13161    to use other calls if you don't.  Clocks will be less accurate if
13162    you have neither ftime nor gettimeofday.
13163 */
13164
13165 /* VS 2008 requires the #include outside of the function */
13166 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13167 #include <sys/timeb.h>
13168 #endif
13169
13170 /* Get the current time as a TimeMark */
13171 void
13172 GetTimeMark(tm)
13173      TimeMark *tm;
13174 {
13175 #if HAVE_GETTIMEOFDAY
13176
13177     struct timeval timeVal;
13178     struct timezone timeZone;
13179
13180     gettimeofday(&timeVal, &timeZone);
13181     tm->sec = (long) timeVal.tv_sec; 
13182     tm->ms = (int) (timeVal.tv_usec / 1000L);
13183
13184 #else /*!HAVE_GETTIMEOFDAY*/
13185 #if HAVE_FTIME
13186
13187 // include <sys/timeb.h> / moved to just above start of function
13188     struct timeb timeB;
13189
13190     ftime(&timeB);
13191     tm->sec = (long) timeB.time;
13192     tm->ms = (int) timeB.millitm;
13193
13194 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13195     tm->sec = (long) time(NULL);
13196     tm->ms = 0;
13197 #endif
13198 #endif
13199 }
13200
13201 /* Return the difference in milliseconds between two
13202    time marks.  We assume the difference will fit in a long!
13203 */
13204 long
13205 SubtractTimeMarks(tm2, tm1)
13206      TimeMark *tm2, *tm1;
13207 {
13208     return 1000L*(tm2->sec - tm1->sec) +
13209            (long) (tm2->ms - tm1->ms);
13210 }
13211
13212
13213 /*
13214  * Code to manage the game clocks.
13215  *
13216  * In tournament play, black starts the clock and then white makes a move.
13217  * We give the human user a slight advantage if he is playing white---the
13218  * clocks don't run until he makes his first move, so it takes zero time.
13219  * Also, we don't account for network lag, so we could get out of sync
13220  * with GNU Chess's clock -- but then, referees are always right.  
13221  */
13222
13223 static TimeMark tickStartTM;
13224 static long intendedTickLength;
13225
13226 long
13227 NextTickLength(timeRemaining)
13228      long timeRemaining;
13229 {
13230     long nominalTickLength, nextTickLength;
13231
13232     if (timeRemaining > 0L && timeRemaining <= 10000L)
13233       nominalTickLength = 100L;
13234     else
13235       nominalTickLength = 1000L;
13236     nextTickLength = timeRemaining % nominalTickLength;
13237     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13238
13239     return nextTickLength;
13240 }
13241
13242 /* Adjust clock one minute up or down */
13243 void
13244 AdjustClock(Boolean which, int dir)
13245 {
13246     if(which) blackTimeRemaining += 60000*dir;
13247     else      whiteTimeRemaining += 60000*dir;
13248     DisplayBothClocks();
13249 }
13250
13251 /* Stop clocks and reset to a fresh time control */
13252 void
13253 ResetClocks() 
13254 {
13255     (void) StopClockTimer();
13256     if (appData.icsActive) {
13257         whiteTimeRemaining = blackTimeRemaining = 0;
13258     } else { /* [HGM] correct new time quote for time odds */
13259         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13260         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13261     }
13262     if (whiteFlag || blackFlag) {
13263         DisplayTitle("");
13264         whiteFlag = blackFlag = FALSE;
13265     }
13266     DisplayBothClocks();
13267 }
13268
13269 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13270
13271 /* Decrement running clock by amount of time that has passed */
13272 void
13273 DecrementClocks()
13274 {
13275     long timeRemaining;
13276     long lastTickLength, fudge;
13277     TimeMark now;
13278
13279     if (!appData.clockMode) return;
13280     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13281         
13282     GetTimeMark(&now);
13283
13284     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13285
13286     /* Fudge if we woke up a little too soon */
13287     fudge = intendedTickLength - lastTickLength;
13288     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13289
13290     if (WhiteOnMove(forwardMostMove)) {
13291         if(whiteNPS >= 0) lastTickLength = 0;
13292         timeRemaining = whiteTimeRemaining -= lastTickLength;
13293         DisplayWhiteClock(whiteTimeRemaining - fudge,
13294                           WhiteOnMove(currentMove));
13295     } else {
13296         if(blackNPS >= 0) lastTickLength = 0;
13297         timeRemaining = blackTimeRemaining -= lastTickLength;
13298         DisplayBlackClock(blackTimeRemaining - fudge,
13299                           !WhiteOnMove(currentMove));
13300     }
13301
13302     if (CheckFlags()) return;
13303         
13304     tickStartTM = now;
13305     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13306     StartClockTimer(intendedTickLength);
13307
13308     /* if the time remaining has fallen below the alarm threshold, sound the
13309      * alarm. if the alarm has sounded and (due to a takeback or time control
13310      * with increment) the time remaining has increased to a level above the
13311      * threshold, reset the alarm so it can sound again. 
13312      */
13313     
13314     if (appData.icsActive && appData.icsAlarm) {
13315
13316         /* make sure we are dealing with the user's clock */
13317         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13318                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13319            )) return;
13320
13321         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13322             alarmSounded = FALSE;
13323         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13324             PlayAlarmSound();
13325             alarmSounded = TRUE;
13326         }
13327     }
13328 }
13329
13330
13331 /* A player has just moved, so stop the previously running
13332    clock and (if in clock mode) start the other one.
13333    We redisplay both clocks in case we're in ICS mode, because
13334    ICS gives us an update to both clocks after every move.
13335    Note that this routine is called *after* forwardMostMove
13336    is updated, so the last fractional tick must be subtracted
13337    from the color that is *not* on move now.
13338 */
13339 void
13340 SwitchClocks()
13341 {
13342     long lastTickLength;
13343     TimeMark now;
13344     int flagged = FALSE;
13345
13346     GetTimeMark(&now);
13347
13348     if (StopClockTimer() && appData.clockMode) {
13349         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13350         if (WhiteOnMove(forwardMostMove)) {
13351             if(blackNPS >= 0) lastTickLength = 0;
13352             blackTimeRemaining -= lastTickLength;
13353            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13354 //         if(pvInfoList[forwardMostMove-1].time == -1)
13355                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13356                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13357         } else {
13358            if(whiteNPS >= 0) lastTickLength = 0;
13359            whiteTimeRemaining -= lastTickLength;
13360            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13361 //         if(pvInfoList[forwardMostMove-1].time == -1)
13362                  pvInfoList[forwardMostMove-1].time = 
13363                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13364         }
13365         flagged = CheckFlags();
13366     }
13367     CheckTimeControl();
13368
13369     if (flagged || !appData.clockMode) return;
13370
13371     switch (gameMode) {
13372       case MachinePlaysBlack:
13373       case MachinePlaysWhite:
13374       case BeginningOfGame:
13375         if (pausing) return;
13376         break;
13377
13378       case EditGame:
13379       case PlayFromGameFile:
13380       case IcsExamining:
13381         return;
13382
13383       default:
13384         break;
13385     }
13386
13387     tickStartTM = now;
13388     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13389       whiteTimeRemaining : blackTimeRemaining);
13390     StartClockTimer(intendedTickLength);
13391 }
13392         
13393
13394 /* Stop both clocks */
13395 void
13396 StopClocks()
13397 {       
13398     long lastTickLength;
13399     TimeMark now;
13400
13401     if (!StopClockTimer()) return;
13402     if (!appData.clockMode) return;
13403
13404     GetTimeMark(&now);
13405
13406     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13407     if (WhiteOnMove(forwardMostMove)) {
13408         if(whiteNPS >= 0) lastTickLength = 0;
13409         whiteTimeRemaining -= lastTickLength;
13410         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13411     } else {
13412         if(blackNPS >= 0) lastTickLength = 0;
13413         blackTimeRemaining -= lastTickLength;
13414         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13415     }
13416     CheckFlags();
13417 }
13418         
13419 /* Start clock of player on move.  Time may have been reset, so
13420    if clock is already running, stop and restart it. */
13421 void
13422 StartClocks()
13423 {
13424     (void) StopClockTimer(); /* in case it was running already */
13425     DisplayBothClocks();
13426     if (CheckFlags()) return;
13427
13428     if (!appData.clockMode) return;
13429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13430
13431     GetTimeMark(&tickStartTM);
13432     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13433       whiteTimeRemaining : blackTimeRemaining);
13434
13435    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13436     whiteNPS = blackNPS = -1; 
13437     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13438        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13439         whiteNPS = first.nps;
13440     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13441        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13442         blackNPS = first.nps;
13443     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13444         whiteNPS = second.nps;
13445     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13446         blackNPS = second.nps;
13447     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13448
13449     StartClockTimer(intendedTickLength);
13450 }
13451
13452 char *
13453 TimeString(ms)
13454      long ms;
13455 {
13456     long second, minute, hour, day;
13457     char *sign = "";
13458     static char buf[32];
13459     
13460     if (ms > 0 && ms <= 9900) {
13461       /* convert milliseconds to tenths, rounding up */
13462       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13463
13464       sprintf(buf, " %03.1f ", tenths/10.0);
13465       return buf;
13466     }
13467
13468     /* convert milliseconds to seconds, rounding up */
13469     /* use floating point to avoid strangeness of integer division
13470        with negative dividends on many machines */
13471     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13472
13473     if (second < 0) {
13474         sign = "-";
13475         second = -second;
13476     }
13477     
13478     day = second / (60 * 60 * 24);
13479     second = second % (60 * 60 * 24);
13480     hour = second / (60 * 60);
13481     second = second % (60 * 60);
13482     minute = second / 60;
13483     second = second % 60;
13484     
13485     if (day > 0)
13486       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13487               sign, day, hour, minute, second);
13488     else if (hour > 0)
13489       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13490     else
13491       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13492     
13493     return buf;
13494 }
13495
13496
13497 /*
13498  * This is necessary because some C libraries aren't ANSI C compliant yet.
13499  */
13500 char *
13501 StrStr(string, match)
13502      char *string, *match;
13503 {
13504     int i, length;
13505     
13506     length = strlen(match);
13507     
13508     for (i = strlen(string) - length; i >= 0; i--, string++)
13509       if (!strncmp(match, string, length))
13510         return string;
13511     
13512     return NULL;
13513 }
13514
13515 char *
13516 StrCaseStr(string, match)
13517      char *string, *match;
13518 {
13519     int i, j, length;
13520     
13521     length = strlen(match);
13522     
13523     for (i = strlen(string) - length; i >= 0; i--, string++) {
13524         for (j = 0; j < length; j++) {
13525             if (ToLower(match[j]) != ToLower(string[j]))
13526               break;
13527         }
13528         if (j == length) return string;
13529     }
13530
13531     return NULL;
13532 }
13533
13534 #ifndef _amigados
13535 int
13536 StrCaseCmp(s1, s2)
13537      char *s1, *s2;
13538 {
13539     char c1, c2;
13540     
13541     for (;;) {
13542         c1 = ToLower(*s1++);
13543         c2 = ToLower(*s2++);
13544         if (c1 > c2) return 1;
13545         if (c1 < c2) return -1;
13546         if (c1 == NULLCHAR) return 0;
13547     }
13548 }
13549
13550
13551 int
13552 ToLower(c)
13553      int c;
13554 {
13555     return isupper(c) ? tolower(c) : c;
13556 }
13557
13558
13559 int
13560 ToUpper(c)
13561      int c;
13562 {
13563     return islower(c) ? toupper(c) : c;
13564 }
13565 #endif /* !_amigados    */
13566
13567 char *
13568 StrSave(s)
13569      char *s;
13570 {
13571     char *ret;
13572
13573     if ((ret = (char *) malloc(strlen(s) + 1))) {
13574         strcpy(ret, s);
13575     }
13576     return ret;
13577 }
13578
13579 char *
13580 StrSavePtr(s, savePtr)
13581      char *s, **savePtr;
13582 {
13583     if (*savePtr) {
13584         free(*savePtr);
13585     }
13586     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13587         strcpy(*savePtr, s);
13588     }
13589     return(*savePtr);
13590 }
13591
13592 char *
13593 PGNDate()
13594 {
13595     time_t clock;
13596     struct tm *tm;
13597     char buf[MSG_SIZ];
13598
13599     clock = time((time_t *)NULL);
13600     tm = localtime(&clock);
13601     sprintf(buf, "%04d.%02d.%02d",
13602             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13603     return StrSave(buf);
13604 }
13605
13606
13607 char *
13608 PositionToFEN(move, overrideCastling)
13609      int move;
13610      char *overrideCastling;
13611 {
13612     int i, j, fromX, fromY, toX, toY;
13613     int whiteToPlay;
13614     char buf[128];
13615     char *p, *q;
13616     int emptycount;
13617     ChessSquare piece;
13618
13619     whiteToPlay = (gameMode == EditPosition) ?
13620       !blackPlaysFirst : (move % 2 == 0);
13621     p = buf;
13622
13623     /* Piece placement data */
13624     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13625         emptycount = 0;
13626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13627             if (boards[move][i][j] == EmptySquare) {
13628                 emptycount++;
13629             } else { ChessSquare piece = boards[move][i][j];
13630                 if (emptycount > 0) {
13631                     if(emptycount<10) /* [HGM] can be >= 10 */
13632                         *p++ = '0' + emptycount;
13633                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13634                     emptycount = 0;
13635                 }
13636                 if(PieceToChar(piece) == '+') {
13637                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13638                     *p++ = '+';
13639                     piece = (ChessSquare)(DEMOTED piece);
13640                 } 
13641                 *p++ = PieceToChar(piece);
13642                 if(p[-1] == '~') {
13643                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13644                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13645                     *p++ = '~';
13646                 }
13647             }
13648         }
13649         if (emptycount > 0) {
13650             if(emptycount<10) /* [HGM] can be >= 10 */
13651                 *p++ = '0' + emptycount;
13652             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13653             emptycount = 0;
13654         }
13655         *p++ = '/';
13656     }
13657     *(p - 1) = ' ';
13658
13659     /* [HGM] print Crazyhouse or Shogi holdings */
13660     if( gameInfo.holdingsWidth ) {
13661         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13662         q = p;
13663         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13664             piece = boards[move][i][BOARD_WIDTH-1];
13665             if( piece != EmptySquare )
13666               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13667                   *p++ = PieceToChar(piece);
13668         }
13669         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13670             piece = boards[move][BOARD_HEIGHT-i-1][0];
13671             if( piece != EmptySquare )
13672               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13673                   *p++ = PieceToChar(piece);
13674         }
13675
13676         if( q == p ) *p++ = '-';
13677         *p++ = ']';
13678         *p++ = ' ';
13679     }
13680
13681     /* Active color */
13682     *p++ = whiteToPlay ? 'w' : 'b';
13683     *p++ = ' ';
13684
13685   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13686     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13687   } else {
13688   if(nrCastlingRights) {
13689      q = p;
13690      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13691        /* [HGM] write directly from rights */
13692            if(castlingRights[move][2] >= 0 &&
13693               castlingRights[move][0] >= 0   )
13694                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13695            if(castlingRights[move][2] >= 0 &&
13696               castlingRights[move][1] >= 0   )
13697                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13698            if(castlingRights[move][5] >= 0 &&
13699               castlingRights[move][3] >= 0   )
13700                 *p++ = castlingRights[move][3] + AAA;
13701            if(castlingRights[move][5] >= 0 &&
13702               castlingRights[move][4] >= 0   )
13703                 *p++ = castlingRights[move][4] + AAA;
13704      } else {
13705
13706         /* [HGM] write true castling rights */
13707         if( nrCastlingRights == 6 ) {
13708             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13709                castlingRights[move][2] >= 0  ) *p++ = 'K';
13710             if(castlingRights[move][1] == BOARD_LEFT &&
13711                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13712             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13713                castlingRights[move][5] >= 0  ) *p++ = 'k';
13714             if(castlingRights[move][4] == BOARD_LEFT &&
13715                castlingRights[move][5] >= 0  ) *p++ = 'q';
13716         }
13717      }
13718      if (q == p) *p++ = '-'; /* No castling rights */
13719      *p++ = ' ';
13720   }
13721
13722   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13723      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13724     /* En passant target square */
13725     if (move > backwardMostMove) {
13726         fromX = moveList[move - 1][0] - AAA;
13727         fromY = moveList[move - 1][1] - ONE;
13728         toX = moveList[move - 1][2] - AAA;
13729         toY = moveList[move - 1][3] - ONE;
13730         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13731             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13732             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13733             fromX == toX) {
13734             /* 2-square pawn move just happened */
13735             *p++ = toX + AAA;
13736             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13737         } else {
13738             *p++ = '-';
13739         }
13740     } else if(move == backwardMostMove) {
13741         // [HGM] perhaps we should always do it like this, and forget the above?
13742         if(epStatus[move] >= 0) {
13743             *p++ = epStatus[move] + AAA;
13744             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13745         } else {
13746             *p++ = '-';
13747         }
13748     } else {
13749         *p++ = '-';
13750     }
13751     *p++ = ' ';
13752   }
13753   }
13754
13755     /* [HGM] find reversible plies */
13756     {   int i = 0, j=move;
13757
13758         if (appData.debugMode) { int k;
13759             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13760             for(k=backwardMostMove; k<=forwardMostMove; k++)
13761                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13762
13763         }
13764
13765         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13766         if( j == backwardMostMove ) i += initialRulePlies;
13767         sprintf(p, "%d ", i);
13768         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13769     }
13770     /* Fullmove number */
13771     sprintf(p, "%d", (move / 2) + 1);
13772     
13773     return StrSave(buf);
13774 }
13775
13776 Boolean
13777 ParseFEN(board, blackPlaysFirst, fen)
13778     Board board;
13779      int *blackPlaysFirst;
13780      char *fen;
13781 {
13782     int i, j;
13783     char *p;
13784     int emptycount;
13785     ChessSquare piece;
13786
13787     p = fen;
13788
13789     /* [HGM] by default clear Crazyhouse holdings, if present */
13790     if(gameInfo.holdingsWidth) {
13791        for(i=0; i<BOARD_HEIGHT; i++) {
13792            board[i][0]             = EmptySquare; /* black holdings */
13793            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13794            board[i][1]             = (ChessSquare) 0; /* black counts */
13795            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13796        }
13797     }
13798
13799     /* Piece placement data */
13800     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13801         j = 0;
13802         for (;;) {
13803             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13804                 if (*p == '/') p++;
13805                 emptycount = gameInfo.boardWidth - j;
13806                 while (emptycount--)
13807                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13808                 break;
13809 #if(BOARD_SIZE >= 10)
13810             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13811                 p++; emptycount=10;
13812                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13813                 while (emptycount--)
13814                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13815 #endif
13816             } else if (isdigit(*p)) {
13817                 emptycount = *p++ - '0';
13818                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13819                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13820                 while (emptycount--)
13821                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13822             } else if (*p == '+' || isalpha(*p)) {
13823                 if (j >= gameInfo.boardWidth) return FALSE;
13824                 if(*p=='+') {
13825                     piece = CharToPiece(*++p);
13826                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13827                     piece = (ChessSquare) (PROMOTED piece ); p++;
13828                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13829                 } else piece = CharToPiece(*p++);
13830
13831                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13832                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13833                     piece = (ChessSquare) (PROMOTED piece);
13834                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13835                     p++;
13836                 }
13837                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13838             } else {
13839                 return FALSE;
13840             }
13841         }
13842     }
13843     while (*p == '/' || *p == ' ') p++;
13844
13845     /* [HGM] look for Crazyhouse holdings here */
13846     while(*p==' ') p++;
13847     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13848         if(*p == '[') p++;
13849         if(*p == '-' ) *p++; /* empty holdings */ else {
13850             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13851             /* if we would allow FEN reading to set board size, we would   */
13852             /* have to add holdings and shift the board read so far here   */
13853             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13854                 *p++;
13855                 if((int) piece >= (int) BlackPawn ) {
13856                     i = (int)piece - (int)BlackPawn;
13857                     i = PieceToNumber((ChessSquare)i);
13858                     if( i >= gameInfo.holdingsSize ) return FALSE;
13859                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13860                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13861                 } else {
13862                     i = (int)piece - (int)WhitePawn;
13863                     i = PieceToNumber((ChessSquare)i);
13864                     if( i >= gameInfo.holdingsSize ) return FALSE;
13865                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13866                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13867                 }
13868             }
13869         }
13870         if(*p == ']') *p++;
13871     }
13872
13873     while(*p == ' ') p++;
13874
13875     /* Active color */
13876     switch (*p++) {
13877       case 'w':
13878         *blackPlaysFirst = FALSE;
13879         break;
13880       case 'b': 
13881         *blackPlaysFirst = TRUE;
13882         break;
13883       default:
13884         return FALSE;
13885     }
13886
13887     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13888     /* return the extra info in global variiables             */
13889
13890     /* set defaults in case FEN is incomplete */
13891     FENepStatus = EP_UNKNOWN;
13892     for(i=0; i<nrCastlingRights; i++ ) {
13893         FENcastlingRights[i] =
13894             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13895     }   /* assume possible unless obviously impossible */
13896     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13897     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13898     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13899     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13900     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13901     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13902     FENrulePlies = 0;
13903
13904     while(*p==' ') p++;
13905     if(nrCastlingRights) {
13906       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13907           /* castling indicator present, so default becomes no castlings */
13908           for(i=0; i<nrCastlingRights; i++ ) {
13909                  FENcastlingRights[i] = -1;
13910           }
13911       }
13912       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13913              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13914              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13915              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13916         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13917
13918         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13919             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13920             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13921         }
13922         switch(c) {
13923           case'K':
13924               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13925               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13926               FENcastlingRights[2] = whiteKingFile;
13927               break;
13928           case'Q':
13929               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13930               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13931               FENcastlingRights[2] = whiteKingFile;
13932               break;
13933           case'k':
13934               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13935               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13936               FENcastlingRights[5] = blackKingFile;
13937               break;
13938           case'q':
13939               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13940               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13941               FENcastlingRights[5] = blackKingFile;
13942           case '-':
13943               break;
13944           default: /* FRC castlings */
13945               if(c >= 'a') { /* black rights */
13946                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13947                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13948                   if(i == BOARD_RGHT) break;
13949                   FENcastlingRights[5] = i;
13950                   c -= AAA;
13951                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13952                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13953                   if(c > i)
13954                       FENcastlingRights[3] = c;
13955                   else
13956                       FENcastlingRights[4] = c;
13957               } else { /* white rights */
13958                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13959                     if(board[0][i] == WhiteKing) break;
13960                   if(i == BOARD_RGHT) break;
13961                   FENcastlingRights[2] = i;
13962                   c -= AAA - 'a' + 'A';
13963                   if(board[0][c] >= WhiteKing) break;
13964                   if(c > i)
13965                       FENcastlingRights[0] = c;
13966                   else
13967                       FENcastlingRights[1] = c;
13968               }
13969         }
13970       }
13971     if (appData.debugMode) {
13972         fprintf(debugFP, "FEN castling rights:");
13973         for(i=0; i<nrCastlingRights; i++)
13974         fprintf(debugFP, " %d", FENcastlingRights[i]);
13975         fprintf(debugFP, "\n");
13976     }
13977
13978       while(*p==' ') p++;
13979     }
13980
13981     /* read e.p. field in games that know e.p. capture */
13982     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13983        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13984       if(*p=='-') {
13985         p++; FENepStatus = EP_NONE;
13986       } else {
13987          char c = *p++ - AAA;
13988
13989          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13990          if(*p >= '0' && *p <='9') *p++;
13991          FENepStatus = c;
13992       }
13993     }
13994
13995
13996     if(sscanf(p, "%d", &i) == 1) {
13997         FENrulePlies = i; /* 50-move ply counter */
13998         /* (The move number is still ignored)    */
13999     }
14000
14001     return TRUE;
14002 }
14003       
14004 void
14005 EditPositionPasteFEN(char *fen)
14006 {
14007   if (fen != NULL) {
14008     Board initial_position;
14009
14010     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14011       DisplayError(_("Bad FEN position in clipboard"), 0);
14012       return ;
14013     } else {
14014       int savedBlackPlaysFirst = blackPlaysFirst;
14015       EditPositionEvent();
14016       blackPlaysFirst = savedBlackPlaysFirst;
14017       CopyBoard(boards[0], initial_position);
14018           /* [HGM] copy FEN attributes as well */
14019           {   int i;
14020               initialRulePlies = FENrulePlies;
14021               epStatus[0] = FENepStatus;
14022               for( i=0; i<nrCastlingRights; i++ )
14023                   castlingRights[0][i] = FENcastlingRights[i];
14024           }
14025       EditPositionDone(FALSE);
14026       DisplayBothClocks();
14027       DrawPosition(FALSE, boards[currentMove]);
14028     }
14029   }
14030 }
14031
14032 static char cseq[12] = "\\   ";
14033
14034 Boolean set_cont_sequence(char *new_seq)
14035 {
14036     int len;
14037     Boolean ret;
14038
14039     // handle bad attempts to set the sequence
14040         if (!new_seq)
14041                 return 0; // acceptable error - no debug
14042
14043     len = strlen(new_seq);
14044     ret = (len > 0) && (len < sizeof(cseq));
14045     if (ret)
14046         strcpy(cseq, new_seq);
14047     else if (appData.debugMode)
14048         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14049     return ret;
14050 }
14051
14052 /*
14053     reformat a source message so words don't cross the width boundary.  internal
14054     newlines are not removed.  returns the wrapped size (no null character unless
14055     included in source message).  If dest is NULL, only calculate the size required
14056     for the dest buffer.  lp argument indicats line position upon entry, and it's
14057     passed back upon exit.
14058 */
14059 int wrap(char *dest, char *src, int count, int width, int *lp)
14060 {
14061     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14062
14063     cseq_len = strlen(cseq);
14064     old_line = line = *lp;
14065     ansi = len = clen = 0;
14066
14067     for (i=0; i < count; i++)
14068     {
14069         if (src[i] == '\033')
14070             ansi = 1;
14071
14072         // if we hit the width, back up
14073         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14074         {
14075             // store i & len in case the word is too long
14076             old_i = i, old_len = len;
14077
14078             // find the end of the last word
14079             while (i && src[i] != ' ' && src[i] != '\n')
14080             {
14081                 i--;
14082                 len--;
14083             }
14084
14085             // word too long?  restore i & len before splitting it
14086             if ((old_i-i+clen) >= width)
14087             {
14088                 i = old_i;
14089                 len = old_len;
14090             }
14091
14092             // extra space?
14093             if (i && src[i-1] == ' ')
14094                 len--;
14095
14096             if (src[i] != ' ' && src[i] != '\n')
14097             {
14098                 i--;
14099                 if (len)
14100                     len--;
14101             }
14102
14103             // now append the newline and continuation sequence
14104             if (dest)
14105                 dest[len] = '\n';
14106             len++;
14107             if (dest)
14108                 strncpy(dest+len, cseq, cseq_len);
14109             len += cseq_len;
14110             line = cseq_len;
14111             clen = cseq_len;
14112             continue;
14113         }
14114
14115         if (dest)
14116             dest[len] = src[i];
14117         len++;
14118         if (!ansi)
14119             line++;
14120         if (src[i] == '\n')
14121             line = 0;
14122         if (src[i] == 'm')
14123             ansi = 0;
14124     }
14125     if (dest && appData.debugMode)
14126     {
14127         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14128             count, width, line, len, *lp);
14129         show_bytes(debugFP, src, count);
14130         fprintf(debugFP, "\ndest: ");
14131         show_bytes(debugFP, dest, len);
14132         fprintf(debugFP, "\n");
14133     }
14134     *lp = dest ? line : old_line;
14135
14136     return len;
14137 }