Fix OO-castling in FRC after pasting FEN
[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     { float 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         if(gameInfo.variant == VariantTwoKings) {
3704             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3705             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3706             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3707         }
3708     } else { int r;
3709         r = castlingRights[moveNum][0] = initialRights[0];
3710         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3711         r = castlingRights[moveNum][1] = initialRights[1];
3712         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3713         r = castlingRights[moveNum][3] = initialRights[3];
3714         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3715         r = castlingRights[moveNum][4] = initialRights[4];
3716         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3717         /* wildcastle kludge: always assume King has rights */
3718         r = castlingRights[moveNum][2] = initialRights[2];
3719         r = castlingRights[moveNum][5] = initialRights[5];
3720     }
3721     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3722     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3723
3724     
3725     if (ics_getting_history == H_GOT_REQ_HEADER ||
3726         ics_getting_history == H_GOT_UNREQ_HEADER) {
3727         /* This was an initial position from a move list, not
3728            the current position */
3729         return;
3730     }
3731     
3732     /* Update currentMove and known move number limits */
3733     newMove = newGame || moveNum > forwardMostMove;
3734
3735     if (newGame) {
3736         forwardMostMove = backwardMostMove = currentMove = moveNum;
3737         if (gameMode == IcsExamining && moveNum == 0) {
3738           /* Workaround for ICS limitation: we are not told the wild
3739              type when starting to examine a game.  But if we ask for
3740              the move list, the move list header will tell us */
3741             ics_getting_history = H_REQUESTED;
3742             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3743             SendToICS(str);
3744         }
3745     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3746                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3747 #if ZIPPY
3748         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3749         /* [HGM] applied this also to an engine that is silently watching        */
3750         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3751             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3752             gameInfo.variant == currentlyInitializedVariant) {
3753           takeback = forwardMostMove - moveNum;
3754           for (i = 0; i < takeback; i++) {
3755             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3756             SendToProgram("undo\n", &first);
3757           }
3758         }
3759 #endif
3760
3761         forwardMostMove = moveNum;
3762         if (!pausing || currentMove > forwardMostMove)
3763           currentMove = forwardMostMove;
3764     } else {
3765         /* New part of history that is not contiguous with old part */ 
3766         if (pausing && gameMode == IcsExamining) {
3767             pauseExamInvalid = TRUE;
3768             forwardMostMove = pauseExamForwardMostMove;
3769             return;
3770         }
3771         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3772 #if ZIPPY
3773             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3774                 // [HGM] when we will receive the move list we now request, it will be
3775                 // fed to the engine from the first move on. So if the engine is not
3776                 // in the initial position now, bring it there.
3777                 InitChessProgram(&first, 0);
3778             }
3779 #endif
3780             ics_getting_history = H_REQUESTED;
3781             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3782             SendToICS(str);
3783         }
3784         forwardMostMove = backwardMostMove = currentMove = moveNum;
3785     }
3786     
3787     /* Update the clocks */
3788     if (strchr(elapsed_time, '.')) {
3789       /* Time is in ms */
3790       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3791       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3792     } else {
3793       /* Time is in seconds */
3794       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3795       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3796     }
3797       
3798
3799 #if ZIPPY
3800     if (appData.zippyPlay && newGame &&
3801         gameMode != IcsObserving && gameMode != IcsIdle &&
3802         gameMode != IcsExamining)
3803       ZippyFirstBoard(moveNum, basetime, increment);
3804 #endif
3805     
3806     /* Put the move on the move list, first converting
3807        to canonical algebraic form. */
3808     if (moveNum > 0) {
3809   if (appData.debugMode) {
3810     if (appData.debugMode) { int f = forwardMostMove;
3811         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3812                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3813     }
3814     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3815     fprintf(debugFP, "moveNum = %d\n", moveNum);
3816     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3817     setbuf(debugFP, NULL);
3818   }
3819         if (moveNum <= backwardMostMove) {
3820             /* We don't know what the board looked like before
3821                this move.  Punt. */
3822             strcpy(parseList[moveNum - 1], move_str);
3823             strcat(parseList[moveNum - 1], " ");
3824             strcat(parseList[moveNum - 1], elapsed_time);
3825             moveList[moveNum - 1][0] = NULLCHAR;
3826         } else if (strcmp(move_str, "none") == 0) {
3827             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3828             /* Again, we don't know what the board looked like;
3829                this is really the start of the game. */
3830             parseList[moveNum - 1][0] = NULLCHAR;
3831             moveList[moveNum - 1][0] = NULLCHAR;
3832             backwardMostMove = moveNum;
3833             startedFromSetupPosition = TRUE;
3834             fromX = fromY = toX = toY = -1;
3835         } else {
3836           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3837           //                 So we parse the long-algebraic move string in stead of the SAN move
3838           int valid; char buf[MSG_SIZ], *prom;
3839
3840           // str looks something like "Q/a1-a2"; kill the slash
3841           if(str[1] == '/') 
3842                 sprintf(buf, "%c%s", str[0], str+2);
3843           else  strcpy(buf, str); // might be castling
3844           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3845                 strcat(buf, prom); // long move lacks promo specification!
3846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3847                 if(appData.debugMode) 
3848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3849                 strcpy(move_str, buf);
3850           }
3851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3852                                 &fromX, &fromY, &toX, &toY, &promoChar)
3853                || ParseOneMove(buf, moveNum - 1, &moveType,
3854                                 &fromX, &fromY, &toX, &toY, &promoChar);
3855           // end of long SAN patch
3856           if (valid) {
3857             (void) CoordsToAlgebraic(boards[moveNum - 1],
3858                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3859                                      fromY, fromX, toY, toX, promoChar,
3860                                      parseList[moveNum-1]);
3861             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3862                              castlingRights[moveNum]) ) {
3863               case MT_NONE:
3864               case MT_STALEMATE:
3865               default:
3866                 break;
3867               case MT_CHECK:
3868                 if(gameInfo.variant != VariantShogi)
3869                     strcat(parseList[moveNum - 1], "+");
3870                 break;
3871               case MT_CHECKMATE:
3872               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3873                 strcat(parseList[moveNum - 1], "#");
3874                 break;
3875             }
3876             strcat(parseList[moveNum - 1], " ");
3877             strcat(parseList[moveNum - 1], elapsed_time);
3878             /* currentMoveString is set as a side-effect of ParseOneMove */
3879             strcpy(moveList[moveNum - 1], currentMoveString);
3880             strcat(moveList[moveNum - 1], "\n");
3881           } else {
3882             /* Move from ICS was illegal!?  Punt. */
3883   if (appData.debugMode) {
3884     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3885     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3886   }
3887             strcpy(parseList[moveNum - 1], move_str);
3888             strcat(parseList[moveNum - 1], " ");
3889             strcat(parseList[moveNum - 1], elapsed_time);
3890             moveList[moveNum - 1][0] = NULLCHAR;
3891             fromX = fromY = toX = toY = -1;
3892           }
3893         }
3894   if (appData.debugMode) {
3895     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3896     setbuf(debugFP, NULL);
3897   }
3898
3899 #if ZIPPY
3900         /* Send move to chess program (BEFORE animating it). */
3901         if (appData.zippyPlay && !newGame && newMove && 
3902            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3903
3904             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3905                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3906                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3907                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3908                             move_str);
3909                     DisplayError(str, 0);
3910                 } else {
3911                     if (first.sendTime) {
3912                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3913                     }
3914                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3915                     if (firstMove && !bookHit) {
3916                         firstMove = FALSE;
3917                         if (first.useColors) {
3918                           SendToProgram(gameMode == IcsPlayingWhite ?
3919                                         "white\ngo\n" :
3920                                         "black\ngo\n", &first);
3921                         } else {
3922                           SendToProgram("go\n", &first);
3923                         }
3924                         first.maybeThinking = TRUE;
3925                     }
3926                 }
3927             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3928               if (moveList[moveNum - 1][0] == NULLCHAR) {
3929                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3930                 DisplayError(str, 0);
3931               } else {
3932                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3933                 SendMoveToProgram(moveNum - 1, &first);
3934               }
3935             }
3936         }
3937 #endif
3938     }
3939
3940     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3941         /* If move comes from a remote source, animate it.  If it
3942            isn't remote, it will have already been animated. */
3943         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3944             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3945         }
3946         if (!pausing && appData.highlightLastMove) {
3947             SetHighlights(fromX, fromY, toX, toY);
3948         }
3949     }
3950     
3951     /* Start the clocks */
3952     whiteFlag = blackFlag = FALSE;
3953     appData.clockMode = !(basetime == 0 && increment == 0);
3954     if (ticking == 0) {
3955       ics_clock_paused = TRUE;
3956       StopClocks();
3957     } else if (ticking == 1) {
3958       ics_clock_paused = FALSE;
3959     }
3960     if (gameMode == IcsIdle ||
3961         relation == RELATION_OBSERVING_STATIC ||
3962         relation == RELATION_EXAMINING ||
3963         ics_clock_paused)
3964       DisplayBothClocks();
3965     else
3966       StartClocks();
3967     
3968     /* Display opponents and material strengths */
3969     if (gameInfo.variant != VariantBughouse &&
3970         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3971         if (tinyLayout || smallLayout) {
3972             if(gameInfo.variant == VariantNormal)
3973                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3974                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3975                     basetime, increment);
3976             else
3977                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3978                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3979                     basetime, increment, (int) gameInfo.variant);
3980         } else {
3981             if(gameInfo.variant == VariantNormal)
3982                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3984                     basetime, increment);
3985             else
3986                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3987                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3988                     basetime, increment, VariantName(gameInfo.variant));
3989         }
3990         DisplayTitle(str);
3991   if (appData.debugMode) {
3992     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3993   }
3994     }
3995
3996    
3997     /* Display the board */
3998     if (!pausing && !appData.noGUI) {
3999       
4000       if (appData.premove)
4001           if (!gotPremove || 
4002              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4003              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4004               ClearPremoveHighlights();
4005
4006       DrawPosition(FALSE, boards[currentMove]);
4007       DisplayMove(moveNum - 1);
4008       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4009             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4010               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4011         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4012       }
4013     }
4014
4015     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4016 #if ZIPPY
4017     if(bookHit) { // [HGM] book: simulate book reply
4018         static char bookMove[MSG_SIZ]; // a bit generous?
4019
4020         programStats.nodes = programStats.depth = programStats.time = 
4021         programStats.score = programStats.got_only_move = 0;
4022         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4023
4024         strcpy(bookMove, "move ");
4025         strcat(bookMove, bookHit);
4026         HandleMachineMove(bookMove, &first);
4027     }
4028 #endif
4029 }
4030
4031 void
4032 GetMoveListEvent()
4033 {
4034     char buf[MSG_SIZ];
4035     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4036         ics_getting_history = H_REQUESTED;
4037         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4038         SendToICS(buf);
4039     }
4040 }
4041
4042 void
4043 AnalysisPeriodicEvent(force)
4044      int force;
4045 {
4046     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4047          && !force) || !appData.periodicUpdates)
4048       return;
4049
4050     /* Send . command to Crafty to collect stats */
4051     SendToProgram(".\n", &first);
4052
4053     /* Don't send another until we get a response (this makes
4054        us stop sending to old Crafty's which don't understand
4055        the "." command (sending illegal cmds resets node count & time,
4056        which looks bad)) */
4057     programStats.ok_to_send = 0;
4058 }
4059
4060 void ics_update_width(new_width)
4061         int new_width;
4062 {
4063         ics_printf("set width %d\n", new_width);
4064 }
4065
4066 void
4067 SendMoveToProgram(moveNum, cps)
4068      int moveNum;
4069      ChessProgramState *cps;
4070 {
4071     char buf[MSG_SIZ];
4072
4073     if (cps->useUsermove) {
4074       SendToProgram("usermove ", cps);
4075     }
4076     if (cps->useSAN) {
4077       char *space;
4078       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4079         int len = space - parseList[moveNum];
4080         memcpy(buf, parseList[moveNum], len);
4081         buf[len++] = '\n';
4082         buf[len] = NULLCHAR;
4083       } else {
4084         sprintf(buf, "%s\n", parseList[moveNum]);
4085       }
4086       SendToProgram(buf, cps);
4087     } else {
4088       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4089         AlphaRank(moveList[moveNum], 4);
4090         SendToProgram(moveList[moveNum], cps);
4091         AlphaRank(moveList[moveNum], 4); // and back
4092       } else
4093       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4094        * the engine. It would be nice to have a better way to identify castle 
4095        * moves here. */
4096       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4097                                                                          && cps->useOOCastle) {
4098         int fromX = moveList[moveNum][0] - AAA; 
4099         int fromY = moveList[moveNum][1] - ONE;
4100         int toX = moveList[moveNum][2] - AAA; 
4101         int toY = moveList[moveNum][3] - ONE;
4102         if((boards[moveNum][fromY][fromX] == WhiteKing 
4103             && boards[moveNum][toY][toX] == WhiteRook)
4104            || (boards[moveNum][fromY][fromX] == BlackKing 
4105                && boards[moveNum][toY][toX] == BlackRook)) {
4106           if(toX > fromX) SendToProgram("O-O\n", cps);
4107           else SendToProgram("O-O-O\n", cps);
4108         }
4109         else SendToProgram(moveList[moveNum], cps);
4110       }
4111       else SendToProgram(moveList[moveNum], cps);
4112       /* End of additions by Tord */
4113     }
4114
4115     /* [HGM] setting up the opening has brought engine in force mode! */
4116     /*       Send 'go' if we are in a mode where machine should play. */
4117     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4118         (gameMode == TwoMachinesPlay   ||
4119 #ifdef ZIPPY
4120          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4121 #endif
4122          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4123         SendToProgram("go\n", cps);
4124   if (appData.debugMode) {
4125     fprintf(debugFP, "(extra)\n");
4126   }
4127     }
4128     setboardSpoiledMachineBlack = 0;
4129 }
4130
4131 void
4132 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4133      ChessMove moveType;
4134      int fromX, fromY, toX, toY;
4135 {
4136     char user_move[MSG_SIZ];
4137
4138     switch (moveType) {
4139       default:
4140         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4141                 (int)moveType, fromX, fromY, toX, toY);
4142         DisplayError(user_move + strlen("say "), 0);
4143         break;
4144       case WhiteKingSideCastle:
4145       case BlackKingSideCastle:
4146       case WhiteQueenSideCastleWild:
4147       case BlackQueenSideCastleWild:
4148       /* PUSH Fabien */
4149       case WhiteHSideCastleFR:
4150       case BlackHSideCastleFR:
4151       /* POP Fabien */
4152         sprintf(user_move, "o-o\n");
4153         break;
4154       case WhiteQueenSideCastle:
4155       case BlackQueenSideCastle:
4156       case WhiteKingSideCastleWild:
4157       case BlackKingSideCastleWild:
4158       /* PUSH Fabien */
4159       case WhiteASideCastleFR:
4160       case BlackASideCastleFR:
4161       /* POP Fabien */
4162         sprintf(user_move, "o-o-o\n");
4163         break;
4164       case WhitePromotionQueen:
4165       case BlackPromotionQueen:
4166       case WhitePromotionRook:
4167       case BlackPromotionRook:
4168       case WhitePromotionBishop:
4169       case BlackPromotionBishop:
4170       case WhitePromotionKnight:
4171       case BlackPromotionKnight:
4172       case WhitePromotionKing:
4173       case BlackPromotionKing:
4174       case WhitePromotionChancellor:
4175       case BlackPromotionChancellor:
4176       case WhitePromotionArchbishop:
4177       case BlackPromotionArchbishop:
4178         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4179             sprintf(user_move, "%c%c%c%c=%c\n",
4180                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4181                 PieceToChar(WhiteFerz));
4182         else if(gameInfo.variant == VariantGreat)
4183             sprintf(user_move, "%c%c%c%c=%c\n",
4184                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4185                 PieceToChar(WhiteMan));
4186         else
4187             sprintf(user_move, "%c%c%c%c=%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189                 PieceToChar(PromoPiece(moveType)));
4190         break;
4191       case WhiteDrop:
4192       case BlackDrop:
4193         sprintf(user_move, "%c@%c%c\n",
4194                 ToUpper(PieceToChar((ChessSquare) fromX)),
4195                 AAA + toX, ONE + toY);
4196         break;
4197       case NormalMove:
4198       case WhiteCapturesEnPassant:
4199       case BlackCapturesEnPassant:
4200       case IllegalMove:  /* could be a variant we don't quite understand */
4201         sprintf(user_move, "%c%c%c%c\n",
4202                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4203         break;
4204     }
4205     SendToICS(user_move);
4206     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4207         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4208 }
4209
4210 void
4211 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4212      int rf, ff, rt, ft;
4213      char promoChar;
4214      char move[7];
4215 {
4216     if (rf == DROP_RANK) {
4217         sprintf(move, "%c@%c%c\n",
4218                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4219     } else {
4220         if (promoChar == 'x' || promoChar == NULLCHAR) {
4221             sprintf(move, "%c%c%c%c\n",
4222                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4223         } else {
4224             sprintf(move, "%c%c%c%c%c\n",
4225                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4226         }
4227     }
4228 }
4229
4230 void
4231 ProcessICSInitScript(f)
4232      FILE *f;
4233 {
4234     char buf[MSG_SIZ];
4235
4236     while (fgets(buf, MSG_SIZ, f)) {
4237         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4238     }
4239
4240     fclose(f);
4241 }
4242
4243
4244 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4245 void
4246 AlphaRank(char *move, int n)
4247 {
4248 //    char *p = move, c; int x, y;
4249
4250     if (appData.debugMode) {
4251         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4252     }
4253
4254     if(move[1]=='*' && 
4255        move[2]>='0' && move[2]<='9' &&
4256        move[3]>='a' && move[3]<='x'    ) {
4257         move[1] = '@';
4258         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4259         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4260     } else
4261     if(move[0]>='0' && move[0]<='9' &&
4262        move[1]>='a' && move[1]<='x' &&
4263        move[2]>='0' && move[2]<='9' &&
4264        move[3]>='a' && move[3]<='x'    ) {
4265         /* input move, Shogi -> normal */
4266         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4267         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4268         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4269         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4270     } else
4271     if(move[1]=='@' &&
4272        move[3]>='0' && move[3]<='9' &&
4273        move[2]>='a' && move[2]<='x'    ) {
4274         move[1] = '*';
4275         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4276         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4277     } else
4278     if(
4279        move[0]>='a' && move[0]<='x' &&
4280        move[3]>='0' && move[3]<='9' &&
4281        move[2]>='a' && move[2]<='x'    ) {
4282          /* output move, normal -> Shogi */
4283         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4284         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4285         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4286         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4287         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4288     }
4289     if (appData.debugMode) {
4290         fprintf(debugFP, "   out = '%s'\n", move);
4291     }
4292 }
4293
4294 /* Parser for moves from gnuchess, ICS, or user typein box */
4295 Boolean
4296 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4297      char *move;
4298      int moveNum;
4299      ChessMove *moveType;
4300      int *fromX, *fromY, *toX, *toY;
4301      char *promoChar;
4302 {       
4303     if (appData.debugMode) {
4304         fprintf(debugFP, "move to parse: %s\n", move);
4305     }
4306     *moveType = yylexstr(moveNum, move);
4307
4308     switch (*moveType) {
4309       case WhitePromotionChancellor:
4310       case BlackPromotionChancellor:
4311       case WhitePromotionArchbishop:
4312       case BlackPromotionArchbishop:
4313       case WhitePromotionQueen:
4314       case BlackPromotionQueen:
4315       case WhitePromotionRook:
4316       case BlackPromotionRook:
4317       case WhitePromotionBishop:
4318       case BlackPromotionBishop:
4319       case WhitePromotionKnight:
4320       case BlackPromotionKnight:
4321       case WhitePromotionKing:
4322       case BlackPromotionKing:
4323       case NormalMove:
4324       case WhiteCapturesEnPassant:
4325       case BlackCapturesEnPassant:
4326       case WhiteKingSideCastle:
4327       case WhiteQueenSideCastle:
4328       case BlackKingSideCastle:
4329       case BlackQueenSideCastle:
4330       case WhiteKingSideCastleWild:
4331       case WhiteQueenSideCastleWild:
4332       case BlackKingSideCastleWild:
4333       case BlackQueenSideCastleWild:
4334       /* Code added by Tord: */
4335       case WhiteHSideCastleFR:
4336       case WhiteASideCastleFR:
4337       case BlackHSideCastleFR:
4338       case BlackASideCastleFR:
4339       /* End of code added by Tord */
4340       case IllegalMove:         /* bug or odd chess variant */
4341         *fromX = currentMoveString[0] - AAA;
4342         *fromY = currentMoveString[1] - ONE;
4343         *toX = currentMoveString[2] - AAA;
4344         *toY = currentMoveString[3] - ONE;
4345         *promoChar = currentMoveString[4];
4346         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4347             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4348     if (appData.debugMode) {
4349         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4350     }
4351             *fromX = *fromY = *toX = *toY = 0;
4352             return FALSE;
4353         }
4354         if (appData.testLegality) {
4355           return (*moveType != IllegalMove);
4356         } else {
4357           return !(*fromX == *toX && *fromY == *toY);
4358         }
4359
4360       case WhiteDrop:
4361       case BlackDrop:
4362         *fromX = *moveType == WhiteDrop ?
4363           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4364           (int) CharToPiece(ToLower(currentMoveString[0]));
4365         *fromY = DROP_RANK;
4366         *toX = currentMoveString[2] - AAA;
4367         *toY = currentMoveString[3] - ONE;
4368         *promoChar = NULLCHAR;
4369         return TRUE;
4370
4371       case AmbiguousMove:
4372       case ImpossibleMove:
4373       case (ChessMove) 0:       /* end of file */
4374       case ElapsedTime:
4375       case Comment:
4376       case PGNTag:
4377       case NAG:
4378       case WhiteWins:
4379       case BlackWins:
4380       case GameIsDrawn:
4381       default:
4382     if (appData.debugMode) {
4383         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4384     }
4385         /* bug? */
4386         *fromX = *fromY = *toX = *toY = 0;
4387         *promoChar = NULLCHAR;
4388         return FALSE;
4389     }
4390 }
4391
4392 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4393 // All positions will have equal probability, but the current method will not provide a unique
4394 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4395 #define DARK 1
4396 #define LITE 2
4397 #define ANY 3
4398
4399 int squaresLeft[4];
4400 int piecesLeft[(int)BlackPawn];
4401 int seed, nrOfShuffles;
4402
4403 void GetPositionNumber()
4404 {       // sets global variable seed
4405         int i;
4406
4407         seed = appData.defaultFrcPosition;
4408         if(seed < 0) { // randomize based on time for negative FRC position numbers
4409                 for(i=0; i<50; i++) seed += random();
4410                 seed = random() ^ random() >> 8 ^ random() << 8;
4411                 if(seed<0) seed = -seed;
4412         }
4413 }
4414
4415 int put(Board board, int pieceType, int rank, int n, int shade)
4416 // put the piece on the (n-1)-th empty squares of the given shade
4417 {
4418         int i;
4419
4420         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4421                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4422                         board[rank][i] = (ChessSquare) pieceType;
4423                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4424                         squaresLeft[ANY]--;
4425                         piecesLeft[pieceType]--; 
4426                         return i;
4427                 }
4428         }
4429         return -1;
4430 }
4431
4432
4433 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4434 // calculate where the next piece goes, (any empty square), and put it there
4435 {
4436         int i;
4437
4438         i = seed % squaresLeft[shade];
4439         nrOfShuffles *= squaresLeft[shade];
4440         seed /= squaresLeft[shade];
4441         put(board, pieceType, rank, i, shade);
4442 }
4443
4444 void AddTwoPieces(Board board, int pieceType, int rank)
4445 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4446 {
4447         int i, n=squaresLeft[ANY], j=n-1, k;
4448
4449         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4450         i = seed % k;  // pick one
4451         nrOfShuffles *= k;
4452         seed /= k;
4453         while(i >= j) i -= j--;
4454         j = n - 1 - j; i += j;
4455         put(board, pieceType, rank, j, ANY);
4456         put(board, pieceType, rank, i, ANY);
4457 }
4458
4459 void SetUpShuffle(Board board, int number)
4460 {
4461         int i, p, first=1;
4462
4463         GetPositionNumber(); nrOfShuffles = 1;
4464
4465         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4466         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4467         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4468
4469         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4470
4471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4472             p = (int) board[0][i];
4473             if(p < (int) BlackPawn) piecesLeft[p] ++;
4474             board[0][i] = EmptySquare;
4475         }
4476
4477         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4478             // shuffles restricted to allow normal castling put KRR first
4479             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4480                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4481             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4482                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4483             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4484                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4485             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4486                 put(board, WhiteRook, 0, 0, ANY);
4487             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4488         }
4489
4490         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4491             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4492             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4493                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4494                 while(piecesLeft[p] >= 2) {
4495                     AddOnePiece(board, p, 0, LITE);
4496                     AddOnePiece(board, p, 0, DARK);
4497                 }
4498                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4499             }
4500
4501         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4502             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4503             // but we leave King and Rooks for last, to possibly obey FRC restriction
4504             if(p == (int)WhiteRook) continue;
4505             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4506             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4507         }
4508
4509         // now everything is placed, except perhaps King (Unicorn) and Rooks
4510
4511         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4512             // Last King gets castling rights
4513             while(piecesLeft[(int)WhiteUnicorn]) {
4514                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4515                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4516             }
4517
4518             while(piecesLeft[(int)WhiteKing]) {
4519                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4520                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4521             }
4522
4523
4524         } else {
4525             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4526             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4527         }
4528
4529         // Only Rooks can be left; simply place them all
4530         while(piecesLeft[(int)WhiteRook]) {
4531                 i = put(board, WhiteRook, 0, 0, ANY);
4532                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4533                         if(first) {
4534                                 first=0;
4535                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4536                         }
4537                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4538                 }
4539         }
4540         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4541             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4542         }
4543
4544         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4545 }
4546
4547 int SetCharTable( char *table, const char * map )
4548 /* [HGM] moved here from winboard.c because of its general usefulness */
4549 /*       Basically a safe strcpy that uses the last character as King */
4550 {
4551     int result = FALSE; int NrPieces;
4552
4553     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4554                     && NrPieces >= 12 && !(NrPieces&1)) {
4555         int i; /* [HGM] Accept even length from 12 to 34 */
4556
4557         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4558         for( i=0; i<NrPieces/2-1; i++ ) {
4559             table[i] = map[i];
4560             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4561         }
4562         table[(int) WhiteKing]  = map[NrPieces/2-1];
4563         table[(int) BlackKing]  = map[NrPieces-1];
4564
4565         result = TRUE;
4566     }
4567
4568     return result;
4569 }
4570
4571 void Prelude(Board board)
4572 {       // [HGM] superchess: random selection of exo-pieces
4573         int i, j, k; ChessSquare p; 
4574         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4575
4576         GetPositionNumber(); // use FRC position number
4577
4578         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4579             SetCharTable(pieceToChar, appData.pieceToCharTable);
4580             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4581                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4582         }
4583
4584         j = seed%4;                 seed /= 4; 
4585         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4588         j = seed%3 + (seed%3 >= j); seed /= 3; 
4589         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4592         j = seed%3;                 seed /= 3; 
4593         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4594         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4595         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4596         j = seed%2 + (seed%2 >= j); seed /= 2; 
4597         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4598         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4599         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4600         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4601         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4602         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4603         put(board, exoPieces[0],    0, 0, ANY);
4604         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4605 }
4606
4607 void
4608 InitPosition(redraw)
4609      int redraw;
4610 {
4611     ChessSquare (* pieces)[BOARD_SIZE];
4612     int i, j, pawnRow, overrule,
4613     oldx = gameInfo.boardWidth,
4614     oldy = gameInfo.boardHeight,
4615     oldh = gameInfo.holdingsWidth,
4616     oldv = gameInfo.variant;
4617
4618     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4619
4620     /* [AS] Initialize pv info list [HGM] and game status */
4621     {
4622         for( i=0; i<MAX_MOVES; i++ ) {
4623             pvInfoList[i].depth = 0;
4624             epStatus[i]=EP_NONE;
4625             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4626         }
4627
4628         initialRulePlies = 0; /* 50-move counter start */
4629
4630         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4631         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4632     }
4633
4634     
4635     /* [HGM] logic here is completely changed. In stead of full positions */
4636     /* the initialized data only consist of the two backranks. The switch */
4637     /* selects which one we will use, which is than copied to the Board   */
4638     /* initialPosition, which for the rest is initialized by Pawns and    */
4639     /* empty squares. This initial position is then copied to boards[0],  */
4640     /* possibly after shuffling, so that it remains available.            */
4641
4642     gameInfo.holdingsWidth = 0; /* default board sizes */
4643     gameInfo.boardWidth    = 8;
4644     gameInfo.boardHeight   = 8;
4645     gameInfo.holdingsSize  = 0;
4646     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4647     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4648     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4649
4650     switch (gameInfo.variant) {
4651     case VariantFischeRandom:
4652       shuffleOpenings = TRUE;
4653     default:
4654       pieces = FIDEArray;
4655       break;
4656     case VariantShatranj:
4657       pieces = ShatranjArray;
4658       nrCastlingRights = 0;
4659       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4660       break;
4661     case VariantTwoKings:
4662       pieces = twoKingsArray;
4663       break;
4664     case VariantCapaRandom:
4665       shuffleOpenings = TRUE;
4666     case VariantCapablanca:
4667       pieces = CapablancaArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4670       break;
4671     case VariantGothic:
4672       pieces = GothicArray;
4673       gameInfo.boardWidth = 10;
4674       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4675       break;
4676     case VariantJanus:
4677       pieces = JanusArray;
4678       gameInfo.boardWidth = 10;
4679       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4680       nrCastlingRights = 6;
4681         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4682         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4683         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4684         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4685         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4686         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4687       break;
4688     case VariantFalcon:
4689       pieces = FalconArray;
4690       gameInfo.boardWidth = 10;
4691       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4692       break;
4693     case VariantXiangqi:
4694       pieces = XiangqiArray;
4695       gameInfo.boardWidth  = 9;
4696       gameInfo.boardHeight = 10;
4697       nrCastlingRights = 0;
4698       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4699       break;
4700     case VariantShogi:
4701       pieces = ShogiArray;
4702       gameInfo.boardWidth  = 9;
4703       gameInfo.boardHeight = 9;
4704       gameInfo.holdingsSize = 7;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4707       break;
4708     case VariantCourier:
4709       pieces = CourierArray;
4710       gameInfo.boardWidth  = 12;
4711       nrCastlingRights = 0;
4712       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4713       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4714       break;
4715     case VariantKnightmate:
4716       pieces = KnightmateArray;
4717       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4718       break;
4719     case VariantFairy:
4720       pieces = fairyArray;
4721       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4722       break;
4723     case VariantGreat:
4724       pieces = GreatArray;
4725       gameInfo.boardWidth = 10;
4726       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4727       gameInfo.holdingsSize = 8;
4728       break;
4729     case VariantSuper:
4730       pieces = FIDEArray;
4731       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4732       gameInfo.holdingsSize = 8;
4733       startedFromSetupPosition = TRUE;
4734       break;
4735     case VariantCrazyhouse:
4736     case VariantBughouse:
4737       pieces = FIDEArray;
4738       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4739       gameInfo.holdingsSize = 5;
4740       break;
4741     case VariantWildCastle:
4742       pieces = FIDEArray;
4743       /* !!?shuffle with kings guaranteed to be on d or e file */
4744       shuffleOpenings = 1;
4745       break;
4746     case VariantNoCastle:
4747       pieces = FIDEArray;
4748       nrCastlingRights = 0;
4749       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4750       /* !!?unconstrained back-rank shuffle */
4751       shuffleOpenings = 1;
4752       break;
4753     }
4754
4755     overrule = 0;
4756     if(appData.NrFiles >= 0) {
4757         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4758         gameInfo.boardWidth = appData.NrFiles;
4759     }
4760     if(appData.NrRanks >= 0) {
4761         gameInfo.boardHeight = appData.NrRanks;
4762     }
4763     if(appData.holdingsSize >= 0) {
4764         i = appData.holdingsSize;
4765         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4766         gameInfo.holdingsSize = i;
4767     }
4768     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4769     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4770         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4771
4772     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4773     if(pawnRow < 1) pawnRow = 1;
4774
4775     /* User pieceToChar list overrules defaults */
4776     if(appData.pieceToCharTable != NULL)
4777         SetCharTable(pieceToChar, appData.pieceToCharTable);
4778
4779     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4780
4781         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4782             s = (ChessSquare) 0; /* account holding counts in guard band */
4783         for( i=0; i<BOARD_HEIGHT; i++ )
4784             initialPosition[i][j] = s;
4785
4786         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4787         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4788         initialPosition[pawnRow][j] = WhitePawn;
4789         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4790         if(gameInfo.variant == VariantXiangqi) {
4791             if(j&1) {
4792                 initialPosition[pawnRow][j] = 
4793                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4794                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4795                    initialPosition[2][j] = WhiteCannon;
4796                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4797                 }
4798             }
4799         }
4800         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4801     }
4802     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4803
4804             j=BOARD_LEFT+1;
4805             initialPosition[1][j] = WhiteBishop;
4806             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4807             j=BOARD_RGHT-2;
4808             initialPosition[1][j] = WhiteRook;
4809             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4810     }
4811
4812     if( nrCastlingRights == -1) {
4813         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4814         /*       This sets default castling rights from none to normal corners   */
4815         /* Variants with other castling rights must set them themselves above    */
4816         nrCastlingRights = 6;
4817        
4818         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4819         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4820         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4821         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4822         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4823         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4824      }
4825
4826      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4827      if(gameInfo.variant == VariantGreat) { // promotion commoners
4828         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4829         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4830         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4831         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4832      }
4833   if (appData.debugMode) {
4834     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4835   }
4836     if(shuffleOpenings) {
4837         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4838         startedFromSetupPosition = TRUE;
4839     }
4840     if(startedFromPositionFile) {
4841       /* [HGM] loadPos: use PositionFile for every new game */
4842       CopyBoard(initialPosition, filePosition);
4843       for(i=0; i<nrCastlingRights; i++)
4844           castlingRights[0][i] = initialRights[i] = fileRights[i];
4845       startedFromSetupPosition = TRUE;
4846     }
4847
4848     CopyBoard(boards[0], initialPosition);
4849
4850     if(oldx != gameInfo.boardWidth ||
4851        oldy != gameInfo.boardHeight ||
4852        oldh != gameInfo.holdingsWidth
4853 #ifdef GOTHIC
4854        || oldv == VariantGothic ||        // For licensing popups
4855        gameInfo.variant == VariantGothic
4856 #endif
4857 #ifdef FALCON
4858        || oldv == VariantFalcon ||
4859        gameInfo.variant == VariantFalcon
4860 #endif
4861                                          )
4862             InitDrawingSizes(-2 ,0);
4863
4864     if (redraw)
4865       DrawPosition(TRUE, boards[currentMove]);
4866 }
4867
4868 void
4869 SendBoard(cps, moveNum)
4870      ChessProgramState *cps;
4871      int moveNum;
4872 {
4873     char message[MSG_SIZ];
4874     
4875     if (cps->useSetboard) {
4876       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4877       sprintf(message, "setboard %s\n", fen);
4878       SendToProgram(message, cps);
4879       free(fen);
4880
4881     } else {
4882       ChessSquare *bp;
4883       int i, j;
4884       /* Kludge to set black to move, avoiding the troublesome and now
4885        * deprecated "black" command.
4886        */
4887       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4888
4889       SendToProgram("edit\n", cps);
4890       SendToProgram("#\n", cps);
4891       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4892         bp = &boards[moveNum][i][BOARD_LEFT];
4893         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4894           if ((int) *bp < (int) BlackPawn) {
4895             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4896                     AAA + j, ONE + i);
4897             if(message[0] == '+' || message[0] == '~') {
4898                 sprintf(message, "%c%c%c+\n",
4899                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4900                         AAA + j, ONE + i);
4901             }
4902             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4903                 message[1] = BOARD_RGHT   - 1 - j + '1';
4904                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4905             }
4906             SendToProgram(message, cps);
4907           }
4908         }
4909       }
4910     
4911       SendToProgram("c\n", cps);
4912       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4913         bp = &boards[moveNum][i][BOARD_LEFT];
4914         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4915           if (((int) *bp != (int) EmptySquare)
4916               && ((int) *bp >= (int) BlackPawn)) {
4917             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4918                     AAA + j, ONE + i);
4919             if(message[0] == '+' || message[0] == '~') {
4920                 sprintf(message, "%c%c%c+\n",
4921                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4922                         AAA + j, ONE + i);
4923             }
4924             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4925                 message[1] = BOARD_RGHT   - 1 - j + '1';
4926                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4927             }
4928             SendToProgram(message, cps);
4929           }
4930         }
4931       }
4932     
4933       SendToProgram(".\n", cps);
4934     }
4935     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4936 }
4937
4938 int
4939 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4940 {
4941     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4942     /* [HGM] add Shogi promotions */
4943     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4944     ChessSquare piece;
4945     ChessMove moveType;
4946     Boolean premove;
4947
4948     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4949     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4950
4951     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4952       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4953         return FALSE;
4954
4955     piece = boards[currentMove][fromY][fromX];
4956     if(gameInfo.variant == VariantShogi) {
4957         promotionZoneSize = 3;
4958         highestPromotingPiece = (int)WhiteFerz;
4959     }
4960
4961     // next weed out all moves that do not touch the promotion zone at all
4962     if((int)piece >= BlackPawn) {
4963         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4964              return FALSE;
4965         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4966     } else {
4967         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4968            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4969     }
4970
4971     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4972
4973     // weed out mandatory Shogi promotions
4974     if(gameInfo.variant == VariantShogi) {
4975         if(piece >= BlackPawn) {
4976             if(toY == 0 && piece == BlackPawn ||
4977                toY == 0 && piece == BlackQueen ||
4978                toY <= 1 && piece == BlackKnight) {
4979                 *promoChoice = '+';
4980                 return FALSE;
4981             }
4982         } else {
4983             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4984                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4985                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4986                 *promoChoice = '+';
4987                 return FALSE;
4988             }
4989         }
4990     }
4991
4992     // weed out obviously illegal Pawn moves
4993     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4994         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4995         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4996         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4997         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4998         // note we are not allowed to test for valid (non-)capture, due to premove
4999     }
5000
5001     // we either have a choice what to promote to, or (in Shogi) whether to promote
5002     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5003         *promoChoice = PieceToChar(BlackFerz);  // no choice
5004         return FALSE;
5005     }
5006     if(appData.alwaysPromoteToQueen) { // predetermined
5007         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5008              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5009         else *promoChoice = PieceToChar(BlackQueen);
5010         return FALSE;
5011     }
5012
5013     // suppress promotion popup on illegal moves that are not premoves
5014     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5015               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5016     if(appData.testLegality && !premove) {
5017         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5018                         epStatus[currentMove], castlingRights[currentMove],
5019                         fromY, fromX, toY, toX, NULLCHAR);
5020         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5021            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5022             return FALSE;
5023     }
5024
5025     return TRUE;
5026 }
5027
5028 int
5029 InPalace(row, column)
5030      int row, column;
5031 {   /* [HGM] for Xiangqi */
5032     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5033          column < (BOARD_WIDTH + 4)/2 &&
5034          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5035     return FALSE;
5036 }
5037
5038 int
5039 PieceForSquare (x, y)
5040      int x;
5041      int y;
5042 {
5043   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5044      return -1;
5045   else
5046      return boards[currentMove][y][x];
5047 }
5048
5049 int
5050 OKToStartUserMove(x, y)
5051      int x, y;
5052 {
5053     ChessSquare from_piece;
5054     int white_piece;
5055
5056     if (matchMode) return FALSE;
5057     if (gameMode == EditPosition) return TRUE;
5058
5059     if (x >= 0 && y >= 0)
5060       from_piece = boards[currentMove][y][x];
5061     else
5062       from_piece = EmptySquare;
5063
5064     if (from_piece == EmptySquare) return FALSE;
5065
5066     white_piece = (int)from_piece >= (int)WhitePawn &&
5067       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5068
5069     switch (gameMode) {
5070       case PlayFromGameFile:
5071       case AnalyzeFile:
5072       case TwoMachinesPlay:
5073       case EndOfGame:
5074         return FALSE;
5075
5076       case IcsObserving:
5077       case IcsIdle:
5078         return FALSE;
5079
5080       case MachinePlaysWhite:
5081       case IcsPlayingBlack:
5082         if (appData.zippyPlay) return FALSE;
5083         if (white_piece) {
5084             DisplayMoveError(_("You are playing Black"));
5085             return FALSE;
5086         }
5087         break;
5088
5089       case MachinePlaysBlack:
5090       case IcsPlayingWhite:
5091         if (appData.zippyPlay) return FALSE;
5092         if (!white_piece) {
5093             DisplayMoveError(_("You are playing White"));
5094             return FALSE;
5095         }
5096         break;
5097
5098       case EditGame:
5099         if (!white_piece && WhiteOnMove(currentMove)) {
5100             DisplayMoveError(_("It is White's turn"));
5101             return FALSE;
5102         }           
5103         if (white_piece && !WhiteOnMove(currentMove)) {
5104             DisplayMoveError(_("It is Black's turn"));
5105             return FALSE;
5106         }           
5107         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5108             /* Editing correspondence game history */
5109             /* Could disallow this or prompt for confirmation */
5110             cmailOldMove = -1;
5111         }
5112         if (currentMove < forwardMostMove) {
5113             /* Discarding moves */
5114             /* Could prompt for confirmation here,
5115                but I don't think that's such a good idea */
5116             forwardMostMove = currentMove;
5117         }
5118         break;
5119
5120       case BeginningOfGame:
5121         if (appData.icsActive) return FALSE;
5122         if (!appData.noChessProgram) {
5123             if (!white_piece) {
5124                 DisplayMoveError(_("You are playing White"));
5125                 return FALSE;
5126             }
5127         }
5128         break;
5129         
5130       case Training:
5131         if (!white_piece && WhiteOnMove(currentMove)) {
5132             DisplayMoveError(_("It is White's turn"));
5133             return FALSE;
5134         }           
5135         if (white_piece && !WhiteOnMove(currentMove)) {
5136             DisplayMoveError(_("It is Black's turn"));
5137             return FALSE;
5138         }           
5139         break;
5140
5141       default:
5142       case IcsExamining:
5143         break;
5144     }
5145     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5146         && gameMode != AnalyzeFile && gameMode != Training) {
5147         DisplayMoveError(_("Displayed position is not current"));
5148         return FALSE;
5149     }
5150     return TRUE;
5151 }
5152
5153 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5154 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5155 int lastLoadGameUseList = FALSE;
5156 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5157 ChessMove lastLoadGameStart = (ChessMove) 0;
5158
5159 ChessMove
5160 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5161      int fromX, fromY, toX, toY;
5162      int promoChar;
5163      Boolean captureOwn;
5164 {
5165     ChessMove moveType;
5166     ChessSquare pdown, pup;
5167
5168     /* Check if the user is playing in turn.  This is complicated because we
5169        let the user "pick up" a piece before it is his turn.  So the piece he
5170        tried to pick up may have been captured by the time he puts it down!
5171        Therefore we use the color the user is supposed to be playing in this
5172        test, not the color of the piece that is currently on the starting
5173        square---except in EditGame mode, where the user is playing both
5174        sides; fortunately there the capture race can't happen.  (It can
5175        now happen in IcsExamining mode, but that's just too bad.  The user
5176        will get a somewhat confusing message in that case.)
5177        */
5178
5179     switch (gameMode) {
5180       case PlayFromGameFile:
5181       case AnalyzeFile:
5182       case TwoMachinesPlay:
5183       case EndOfGame:
5184       case IcsObserving:
5185       case IcsIdle:
5186         /* We switched into a game mode where moves are not accepted,
5187            perhaps while the mouse button was down. */
5188         return ImpossibleMove;
5189
5190       case MachinePlaysWhite:
5191         /* User is moving for Black */
5192         if (WhiteOnMove(currentMove)) {
5193             DisplayMoveError(_("It is White's turn"));
5194             return ImpossibleMove;
5195         }
5196         break;
5197
5198       case MachinePlaysBlack:
5199         /* User is moving for White */
5200         if (!WhiteOnMove(currentMove)) {
5201             DisplayMoveError(_("It is Black's turn"));
5202             return ImpossibleMove;
5203         }
5204         break;
5205
5206       case EditGame:
5207       case IcsExamining:
5208       case BeginningOfGame:
5209       case AnalyzeMode:
5210       case Training:
5211         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5212             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5213             /* User is moving for Black */
5214             if (WhiteOnMove(currentMove)) {
5215                 DisplayMoveError(_("It is White's turn"));
5216                 return ImpossibleMove;
5217             }
5218         } else {
5219             /* User is moving for White */
5220             if (!WhiteOnMove(currentMove)) {
5221                 DisplayMoveError(_("It is Black's turn"));
5222                 return ImpossibleMove;
5223             }
5224         }
5225         break;
5226
5227       case IcsPlayingBlack:
5228         /* User is moving for Black */
5229         if (WhiteOnMove(currentMove)) {
5230             if (!appData.premove) {
5231                 DisplayMoveError(_("It is White's turn"));
5232             } else if (toX >= 0 && toY >= 0) {
5233                 premoveToX = toX;
5234                 premoveToY = toY;
5235                 premoveFromX = fromX;
5236                 premoveFromY = fromY;
5237                 premovePromoChar = promoChar;
5238                 gotPremove = 1;
5239                 if (appData.debugMode) 
5240                     fprintf(debugFP, "Got premove: fromX %d,"
5241                             "fromY %d, toX %d, toY %d\n",
5242                             fromX, fromY, toX, toY);
5243             }
5244             return ImpossibleMove;
5245         }
5246         break;
5247
5248       case IcsPlayingWhite:
5249         /* User is moving for White */
5250         if (!WhiteOnMove(currentMove)) {
5251             if (!appData.premove) {
5252                 DisplayMoveError(_("It is Black's turn"));
5253             } else if (toX >= 0 && toY >= 0) {
5254                 premoveToX = toX;
5255                 premoveToY = toY;
5256                 premoveFromX = fromX;
5257                 premoveFromY = fromY;
5258                 premovePromoChar = promoChar;
5259                 gotPremove = 1;
5260                 if (appData.debugMode) 
5261                     fprintf(debugFP, "Got premove: fromX %d,"
5262                             "fromY %d, toX %d, toY %d\n",
5263                             fromX, fromY, toX, toY);
5264             }
5265             return ImpossibleMove;
5266         }
5267         break;
5268
5269       default:
5270         break;
5271
5272       case EditPosition:
5273         /* EditPosition, empty square, or different color piece;
5274            click-click move is possible */
5275         if (toX == -2 || toY == -2) {
5276             boards[0][fromY][fromX] = EmptySquare;
5277             return AmbiguousMove;
5278         } else if (toX >= 0 && toY >= 0) {
5279             boards[0][toY][toX] = boards[0][fromY][fromX];
5280             boards[0][fromY][fromX] = EmptySquare;
5281             return AmbiguousMove;
5282         }
5283         return ImpossibleMove;
5284     }
5285
5286     if(toX < 0 || toY < 0) return ImpossibleMove;
5287     pdown = boards[currentMove][fromY][fromX];
5288     pup = boards[currentMove][toY][toX];
5289
5290     /* [HGM] If move started in holdings, it means a drop */
5291     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5292          if( pup != EmptySquare ) return ImpossibleMove;
5293          if(appData.testLegality) {
5294              /* it would be more logical if LegalityTest() also figured out
5295               * which drops are legal. For now we forbid pawns on back rank.
5296               * Shogi is on its own here...
5297               */
5298              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5299                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5300                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301          }
5302          return WhiteDrop; /* Not needed to specify white or black yet */
5303     }
5304
5305     userOfferedDraw = FALSE;
5306         
5307     /* [HGM] always test for legality, to get promotion info */
5308     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5309                           epStatus[currentMove], castlingRights[currentMove],
5310                                          fromY, fromX, toY, toX, promoChar);
5311     /* [HGM] but possibly ignore an IllegalMove result */
5312     if (appData.testLegality) {
5313         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5314             DisplayMoveError(_("Illegal move"));
5315             return ImpossibleMove;
5316         }
5317     }
5318
5319     return moveType;
5320     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5321        function is made into one that returns an OK move type if FinishMove
5322        should be called. This to give the calling driver routine the
5323        opportunity to finish the userMove input with a promotion popup,
5324        without bothering the user with this for invalid or illegal moves */
5325
5326 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5327 }
5328
5329 /* Common tail of UserMoveEvent and DropMenuEvent */
5330 int
5331 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5332      ChessMove moveType;
5333      int fromX, fromY, toX, toY;
5334      /*char*/int promoChar;
5335 {
5336     char *bookHit = 0;
5337
5338     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5339         // [HGM] superchess: suppress promotions to non-available piece
5340         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5341         if(WhiteOnMove(currentMove)) {
5342             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5343         } else {
5344             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5345         }
5346     }
5347
5348     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5349        move type in caller when we know the move is a legal promotion */
5350     if(moveType == NormalMove && promoChar)
5351         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5352
5353     /* [HGM] convert drag-and-drop piece drops to standard form */
5354     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5355          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5356            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5357                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5358            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5359            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5360            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5361            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5362          fromY = DROP_RANK;
5363     }
5364
5365     /* [HGM] <popupFix> The following if has been moved here from
5366        UserMoveEvent(). Because it seemed to belong here (why not allow
5367        piece drops in training games?), and because it can only be
5368        performed after it is known to what we promote. */
5369     if (gameMode == Training) {
5370       /* compare the move played on the board to the next move in the
5371        * game. If they match, display the move and the opponent's response. 
5372        * If they don't match, display an error message.
5373        */
5374       int saveAnimate;
5375       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5376       CopyBoard(testBoard, boards[currentMove]);
5377       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5378
5379       if (CompareBoards(testBoard, boards[currentMove+1])) {
5380         ForwardInner(currentMove+1);
5381
5382         /* Autoplay the opponent's response.
5383          * if appData.animate was TRUE when Training mode was entered,
5384          * the response will be animated.
5385          */
5386         saveAnimate = appData.animate;
5387         appData.animate = animateTraining;
5388         ForwardInner(currentMove+1);
5389         appData.animate = saveAnimate;
5390
5391         /* check for the end of the game */
5392         if (currentMove >= forwardMostMove) {
5393           gameMode = PlayFromGameFile;
5394           ModeHighlight();
5395           SetTrainingModeOff();
5396           DisplayInformation(_("End of game"));
5397         }
5398       } else {
5399         DisplayError(_("Incorrect move"), 0);
5400       }
5401       return 1;
5402     }
5403
5404   /* Ok, now we know that the move is good, so we can kill
5405      the previous line in Analysis Mode */
5406   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5407     forwardMostMove = currentMove;
5408   }
5409
5410   /* If we need the chess program but it's dead, restart it */
5411   ResurrectChessProgram();
5412
5413   /* A user move restarts a paused game*/
5414   if (pausing)
5415     PauseEvent();
5416
5417   thinkOutput[0] = NULLCHAR;
5418
5419   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5420
5421   if (gameMode == BeginningOfGame) {
5422     if (appData.noChessProgram) {
5423       gameMode = EditGame;
5424       SetGameInfo();
5425     } else {
5426       char buf[MSG_SIZ];
5427       gameMode = MachinePlaysBlack;
5428       StartClocks();
5429       SetGameInfo();
5430       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5431       DisplayTitle(buf);
5432       if (first.sendName) {
5433         sprintf(buf, "name %s\n", gameInfo.white);
5434         SendToProgram(buf, &first);
5435       }
5436       StartClocks();
5437     }
5438     ModeHighlight();
5439   }
5440
5441   /* Relay move to ICS or chess engine */
5442   if (appData.icsActive) {
5443     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5444         gameMode == IcsExamining) {
5445       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5446       ics_user_moved = 1;
5447     }
5448   } else {
5449     if (first.sendTime && (gameMode == BeginningOfGame ||
5450                            gameMode == MachinePlaysWhite ||
5451                            gameMode == MachinePlaysBlack)) {
5452       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5453     }
5454     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5455          // [HGM] book: if program might be playing, let it use book
5456         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5457         first.maybeThinking = TRUE;
5458     } else SendMoveToProgram(forwardMostMove-1, &first);
5459     if (currentMove == cmailOldMove + 1) {
5460       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5461     }
5462   }
5463
5464   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5465
5466   switch (gameMode) {
5467   case EditGame:
5468     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5469                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5470     case MT_NONE:
5471     case MT_CHECK:
5472       break;
5473     case MT_CHECKMATE:
5474     case MT_STAINMATE:
5475       if (WhiteOnMove(currentMove)) {
5476         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5477       } else {
5478         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5479       }
5480       break;
5481     case MT_STALEMATE:
5482       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5483       break;
5484     }
5485     break;
5486     
5487   case MachinePlaysBlack:
5488   case MachinePlaysWhite:
5489     /* disable certain menu options while machine is thinking */
5490     SetMachineThinkingEnables();
5491     break;
5492
5493   default:
5494     break;
5495   }
5496
5497   if(bookHit) { // [HGM] book: simulate book reply
5498         static char bookMove[MSG_SIZ]; // a bit generous?
5499
5500         programStats.nodes = programStats.depth = programStats.time = 
5501         programStats.score = programStats.got_only_move = 0;
5502         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5503
5504         strcpy(bookMove, "move ");
5505         strcat(bookMove, bookHit);
5506         HandleMachineMove(bookMove, &first);
5507   }
5508   return 1;
5509 }
5510
5511 void
5512 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5513      int fromX, fromY, toX, toY;
5514      int promoChar;
5515 {
5516     /* [HGM] This routine was added to allow calling of its two logical
5517        parts from other modules in the old way. Before, UserMoveEvent()
5518        automatically called FinishMove() if the move was OK, and returned
5519        otherwise. I separated the two, in order to make it possible to
5520        slip a promotion popup in between. But that it always needs two
5521        calls, to the first part, (now called UserMoveTest() ), and to
5522        FinishMove if the first part succeeded. Calls that do not need
5523        to do anything in between, can call this routine the old way. 
5524     */
5525     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5526 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5527     if(moveType == AmbiguousMove)
5528         DrawPosition(FALSE, boards[currentMove]);
5529     else if(moveType != ImpossibleMove && moveType != Comment)
5530         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5531 }
5532
5533 void LeftClick(ClickType clickType, int xPix, int yPix)
5534 {
5535     int x, y;
5536     Boolean saveAnimate;
5537     static int second = 0, promotionChoice = 0;
5538     char promoChoice = NULLCHAR;
5539
5540     if (clickType == Press) ErrorPopDown();
5541
5542     x = EventToSquare(xPix, BOARD_WIDTH);
5543     y = EventToSquare(yPix, BOARD_HEIGHT);
5544     if (!flipView && y >= 0) {
5545         y = BOARD_HEIGHT - 1 - y;
5546     }
5547     if (flipView && x >= 0) {
5548         x = BOARD_WIDTH - 1 - x;
5549     }
5550
5551     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5552         if(clickType == Release) return; // ignore upclick of click-click destination
5553         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5554         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5555         if(gameInfo.holdingsWidth && 
5556                 (WhiteOnMove(currentMove) 
5557                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5558                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5559             // click in right holdings, for determining promotion piece
5560             ChessSquare p = boards[currentMove][y][x];
5561             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5562             if(p != EmptySquare) {
5563                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5564                 fromX = fromY = -1;
5565                 return;
5566             }
5567         }
5568         DrawPosition(FALSE, boards[currentMove]);
5569         return;
5570     }
5571
5572     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5573     if(clickType == Press
5574             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5575               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5576               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5577         return;
5578
5579     if (fromX == -1) {
5580         if (clickType == Press) {
5581             /* First square */
5582             if (OKToStartUserMove(x, y)) {
5583                 fromX = x;
5584                 fromY = y;
5585                 second = 0;
5586                 DragPieceBegin(xPix, yPix);
5587                 if (appData.highlightDragging) {
5588                     SetHighlights(x, y, -1, -1);
5589                 }
5590             }
5591         }
5592         return;
5593     }
5594
5595     /* fromX != -1 */
5596     if (clickType == Press && gameMode != EditPosition) {
5597         ChessSquare fromP;
5598         ChessSquare toP;
5599         int frc;
5600
5601         // ignore off-board to clicks
5602         if(y < 0 || x < 0) return;
5603
5604         /* Check if clicking again on the same color piece */
5605         fromP = boards[currentMove][fromY][fromX];
5606         toP = boards[currentMove][y][x];
5607         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5608         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5609              WhitePawn <= toP && toP <= WhiteKing &&
5610              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5611              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5612             (BlackPawn <= fromP && fromP <= BlackKing && 
5613              BlackPawn <= toP && toP <= BlackKing &&
5614              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5615              !(fromP == BlackKing && toP == BlackRook && frc))) {
5616             /* Clicked again on same color piece -- changed his mind */
5617             second = (x == fromX && y == fromY);
5618             if (appData.highlightDragging) {
5619                 SetHighlights(x, y, -1, -1);
5620             } else {
5621                 ClearHighlights();
5622             }
5623             if (OKToStartUserMove(x, y)) {
5624                 fromX = x;
5625                 fromY = y;
5626                 DragPieceBegin(xPix, yPix);
5627             }
5628             return;
5629         }
5630         // ignore clicks on holdings
5631         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5632     }
5633
5634     if (clickType == Release && x == fromX && y == fromY) {
5635         DragPieceEnd(xPix, yPix);
5636         if (appData.animateDragging) {
5637             /* Undo animation damage if any */
5638             DrawPosition(FALSE, NULL);
5639         }
5640         if (second) {
5641             /* Second up/down in same square; just abort move */
5642             second = 0;
5643             fromX = fromY = -1;
5644             ClearHighlights();
5645             gotPremove = 0;
5646             ClearPremoveHighlights();
5647         } else {
5648             /* First upclick in same square; start click-click mode */
5649             SetHighlights(x, y, -1, -1);
5650         }
5651         return;
5652     }
5653
5654     /* we now have a different from- and (possibly off-board) to-square */
5655     /* Completed move */
5656     toX = x;
5657     toY = y;
5658     saveAnimate = appData.animate;
5659     if (clickType == Press) {
5660         /* Finish clickclick move */
5661         if (appData.animate || appData.highlightLastMove) {
5662             SetHighlights(fromX, fromY, toX, toY);
5663         } else {
5664             ClearHighlights();
5665         }
5666     } else {
5667         /* Finish drag move */
5668         if (appData.highlightLastMove) {
5669             SetHighlights(fromX, fromY, toX, toY);
5670         } else {
5671             ClearHighlights();
5672         }
5673         DragPieceEnd(xPix, yPix);
5674         /* Don't animate move and drag both */
5675         appData.animate = FALSE;
5676     }
5677
5678     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5679     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5680         ClearHighlights();
5681         fromX = fromY = -1;
5682         DrawPosition(TRUE, NULL);
5683         return;
5684     }
5685
5686     // off-board moves should not be highlighted
5687     if(x < 0 || x < 0) ClearHighlights();
5688
5689     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5690         SetHighlights(fromX, fromY, toX, toY);
5691         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5692             // [HGM] super: promotion to captured piece selected from holdings
5693             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5694             promotionChoice = TRUE;
5695             // kludge follows to temporarily execute move on display, without promoting yet
5696             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5697             boards[currentMove][toY][toX] = p;
5698             DrawPosition(FALSE, boards[currentMove]);
5699             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5700             boards[currentMove][toY][toX] = q;
5701             DisplayMessage("Click in holdings to choose piece", "");
5702             return;
5703         }
5704         PromotionPopUp();
5705     } else {
5706         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5707         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5708         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5709         fromX = fromY = -1;
5710     }
5711     appData.animate = saveAnimate;
5712     if (appData.animate || appData.animateDragging) {
5713         /* Undo animation damage if needed */
5714         DrawPosition(FALSE, NULL);
5715     }
5716 }
5717
5718 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5719 {
5720 //    char * hint = lastHint;
5721     FrontEndProgramStats stats;
5722
5723     stats.which = cps == &first ? 0 : 1;
5724     stats.depth = cpstats->depth;
5725     stats.nodes = cpstats->nodes;
5726     stats.score = cpstats->score;
5727     stats.time = cpstats->time;
5728     stats.pv = cpstats->movelist;
5729     stats.hint = lastHint;
5730     stats.an_move_index = 0;
5731     stats.an_move_count = 0;
5732
5733     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5734         stats.hint = cpstats->move_name;
5735         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5736         stats.an_move_count = cpstats->nr_moves;
5737     }
5738
5739     SetProgramStats( &stats );
5740 }
5741
5742 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5743 {   // [HGM] book: this routine intercepts moves to simulate book replies
5744     char *bookHit = NULL;
5745
5746     //first determine if the incoming move brings opponent into his book
5747     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5748         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5749     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5750     if(bookHit != NULL && !cps->bookSuspend) {
5751         // make sure opponent is not going to reply after receiving move to book position
5752         SendToProgram("force\n", cps);
5753         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5754     }
5755     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5756     // now arrange restart after book miss
5757     if(bookHit) {
5758         // after a book hit we never send 'go', and the code after the call to this routine
5759         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5760         char buf[MSG_SIZ];
5761         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5762         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5763         SendToProgram(buf, cps);
5764         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5765     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5766         SendToProgram("go\n", cps);
5767         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5768     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5769         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5770             SendToProgram("go\n", cps); 
5771         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5772     }
5773     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5774 }
5775
5776 char *savedMessage;
5777 ChessProgramState *savedState;
5778 void DeferredBookMove(void)
5779 {
5780         if(savedState->lastPing != savedState->lastPong)
5781                     ScheduleDelayedEvent(DeferredBookMove, 10);
5782         else
5783         HandleMachineMove(savedMessage, savedState);
5784 }
5785
5786 void
5787 HandleMachineMove(message, cps)
5788      char *message;
5789      ChessProgramState *cps;
5790 {
5791     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5792     char realname[MSG_SIZ];
5793     int fromX, fromY, toX, toY;
5794     ChessMove moveType;
5795     char promoChar;
5796     char *p;
5797     int machineWhite;
5798     char *bookHit;
5799
5800 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5801     /*
5802      * Kludge to ignore BEL characters
5803      */
5804     while (*message == '\007') message++;
5805
5806     /*
5807      * [HGM] engine debug message: ignore lines starting with '#' character
5808      */
5809     if(cps->debug && *message == '#') return;
5810
5811     /*
5812      * Look for book output
5813      */
5814     if (cps == &first && bookRequested) {
5815         if (message[0] == '\t' || message[0] == ' ') {
5816             /* Part of the book output is here; append it */
5817             strcat(bookOutput, message);
5818             strcat(bookOutput, "  \n");
5819             return;
5820         } else if (bookOutput[0] != NULLCHAR) {
5821             /* All of book output has arrived; display it */
5822             char *p = bookOutput;
5823             while (*p != NULLCHAR) {
5824                 if (*p == '\t') *p = ' ';
5825                 p++;
5826             }
5827             DisplayInformation(bookOutput);
5828             bookRequested = FALSE;
5829             /* Fall through to parse the current output */
5830         }
5831     }
5832
5833     /*
5834      * Look for machine move.
5835      */
5836     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5837         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5838     {
5839         /* This method is only useful on engines that support ping */
5840         if (cps->lastPing != cps->lastPong) {
5841           if (gameMode == BeginningOfGame) {
5842             /* Extra move from before last new; ignore */
5843             if (appData.debugMode) {
5844                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5845             }
5846           } else {
5847             if (appData.debugMode) {
5848                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5849                         cps->which, gameMode);
5850             }
5851
5852             SendToProgram("undo\n", cps);
5853           }
5854           return;
5855         }
5856
5857         switch (gameMode) {
5858           case BeginningOfGame:
5859             /* Extra move from before last reset; ignore */
5860             if (appData.debugMode) {
5861                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5862             }
5863             return;
5864
5865           case EndOfGame:
5866           case IcsIdle:
5867           default:
5868             /* Extra move after we tried to stop.  The mode test is
5869                not a reliable way of detecting this problem, but it's
5870                the best we can do on engines that don't support ping.
5871             */
5872             if (appData.debugMode) {
5873                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5874                         cps->which, gameMode);
5875             }
5876             SendToProgram("undo\n", cps);
5877             return;
5878
5879           case MachinePlaysWhite:
5880           case IcsPlayingWhite:
5881             machineWhite = TRUE;
5882             break;
5883
5884           case MachinePlaysBlack:
5885           case IcsPlayingBlack:
5886             machineWhite = FALSE;
5887             break;
5888
5889           case TwoMachinesPlay:
5890             machineWhite = (cps->twoMachinesColor[0] == 'w');
5891             break;
5892         }
5893         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5894             if (appData.debugMode) {
5895                 fprintf(debugFP,
5896                         "Ignoring move out of turn by %s, gameMode %d"
5897                         ", forwardMost %d\n",
5898                         cps->which, gameMode, forwardMostMove);
5899             }
5900             return;
5901         }
5902
5903     if (appData.debugMode) { int f = forwardMostMove;
5904         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5905                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5906     }
5907         if(cps->alphaRank) AlphaRank(machineMove, 4);
5908         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5909                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5910             /* Machine move could not be parsed; ignore it. */
5911             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5912                     machineMove, cps->which);
5913             DisplayError(buf1, 0);
5914             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5915                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5916             if (gameMode == TwoMachinesPlay) {
5917               GameEnds(machineWhite ? BlackWins : WhiteWins,
5918                        buf1, GE_XBOARD);
5919             }
5920             return;
5921         }
5922
5923         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5924         /* So we have to redo legality test with true e.p. status here,  */
5925         /* to make sure an illegal e.p. capture does not slip through,   */
5926         /* to cause a forfeit on a justified illegal-move complaint      */
5927         /* of the opponent.                                              */
5928         if( gameMode==TwoMachinesPlay && appData.testLegality
5929             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5930                                                               ) {
5931            ChessMove moveType;
5932            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5933                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5934                              fromY, fromX, toY, toX, promoChar);
5935             if (appData.debugMode) {
5936                 int i;
5937                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5938                     castlingRights[forwardMostMove][i], castlingRank[i]);
5939                 fprintf(debugFP, "castling rights\n");
5940             }
5941             if(moveType == IllegalMove) {
5942                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5943                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5944                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5945                            buf1, GE_XBOARD);
5946                 return;
5947            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5948            /* [HGM] Kludge to handle engines that send FRC-style castling
5949               when they shouldn't (like TSCP-Gothic) */
5950            switch(moveType) {
5951              case WhiteASideCastleFR:
5952              case BlackASideCastleFR:
5953                toX+=2;
5954                currentMoveString[2]++;
5955                break;
5956              case WhiteHSideCastleFR:
5957              case BlackHSideCastleFR:
5958                toX--;
5959                currentMoveString[2]--;
5960                break;
5961              default: ; // nothing to do, but suppresses warning of pedantic compilers
5962            }
5963         }
5964         hintRequested = FALSE;
5965         lastHint[0] = NULLCHAR;
5966         bookRequested = FALSE;
5967         /* Program may be pondering now */
5968         cps->maybeThinking = TRUE;
5969         if (cps->sendTime == 2) cps->sendTime = 1;
5970         if (cps->offeredDraw) cps->offeredDraw--;
5971
5972 #if ZIPPY
5973         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5974             first.initDone) {
5975           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5976           ics_user_moved = 1;
5977           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5978                 char buf[3*MSG_SIZ];
5979
5980                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5981                         programStats.score / 100.,
5982                         programStats.depth,
5983                         programStats.time / 100.,
5984                         (unsigned int)programStats.nodes,
5985                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5986                         programStats.movelist);
5987                 SendToICS(buf);
5988 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5989           }
5990         }
5991 #endif
5992         /* currentMoveString is set as a side-effect of ParseOneMove */
5993         strcpy(machineMove, currentMoveString);
5994         strcat(machineMove, "\n");
5995         strcpy(moveList[forwardMostMove], machineMove);
5996
5997         /* [AS] Save move info and clear stats for next move */
5998         pvInfoList[ forwardMostMove ].score = programStats.score;
5999         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6000         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6001         ClearProgramStats();
6002         thinkOutput[0] = NULLCHAR;
6003         hiddenThinkOutputState = 0;
6004
6005         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6006
6007         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6008         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6009             int count = 0;
6010
6011             while( count < adjudicateLossPlies ) {
6012                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6013
6014                 if( count & 1 ) {
6015                     score = -score; /* Flip score for winning side */
6016                 }
6017
6018                 if( score > adjudicateLossThreshold ) {
6019                     break;
6020                 }
6021
6022                 count++;
6023             }
6024
6025             if( count >= adjudicateLossPlies ) {
6026                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6027
6028                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6029                     "Xboard adjudication", 
6030                     GE_XBOARD );
6031
6032                 return;
6033             }
6034         }
6035
6036         if( gameMode == TwoMachinesPlay ) {
6037           // [HGM] some adjudications useful with buggy engines
6038             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6039           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6040
6041
6042             if( appData.testLegality )
6043             {   /* [HGM] Some more adjudications for obstinate engines */
6044                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6045                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6046                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6047                 static int moveCount = 6;
6048                 ChessMove result;
6049                 char *reason = NULL;
6050
6051                 /* Count what is on board. */
6052                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6053                 {   ChessSquare p = boards[forwardMostMove][i][j];
6054                     int m=i;
6055
6056                     switch((int) p)
6057                     {   /* count B,N,R and other of each side */
6058                         case WhiteKing:
6059                         case BlackKing:
6060                              NrK++; break; // [HGM] atomic: count Kings
6061                         case WhiteKnight:
6062                              NrWN++; break;
6063                         case WhiteBishop:
6064                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6065                              bishopsColor |= 1 << ((i^j)&1);
6066                              NrWB++; break;
6067                         case BlackKnight:
6068                              NrBN++; break;
6069                         case BlackBishop:
6070                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6071                              bishopsColor |= 1 << ((i^j)&1);
6072                              NrBB++; break;
6073                         case WhiteRook:
6074                              NrWR++; break;
6075                         case BlackRook:
6076                              NrBR++; break;
6077                         case WhiteQueen:
6078                              NrWQ++; break;
6079                         case BlackQueen:
6080                              NrBQ++; break;
6081                         case EmptySquare: 
6082                              break;
6083                         case BlackPawn:
6084                              m = 7-i;
6085                         case WhitePawn:
6086                              PawnAdvance += m; NrPawns++;
6087                     }
6088                     NrPieces += (p != EmptySquare);
6089                     NrW += ((int)p < (int)BlackPawn);
6090                     if(gameInfo.variant == VariantXiangqi && 
6091                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6092                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6093                         NrW -= ((int)p < (int)BlackPawn);
6094                     }
6095                 }
6096
6097                 /* Some material-based adjudications that have to be made before stalemate test */
6098                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6099                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6100                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6101                      if(appData.checkMates) {
6102                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6103                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6104                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6105                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6106                          return;
6107                      }
6108                 }
6109
6110                 /* Bare King in Shatranj (loses) or Losers (wins) */
6111                 if( NrW == 1 || NrPieces - NrW == 1) {
6112                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6113                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6114                      if(appData.checkMates) {
6115                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6116                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6118                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6119                          return;
6120                      }
6121                   } else
6122                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6123                   {    /* bare King */
6124                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6125                         if(appData.checkMates) {
6126                             /* but only adjudicate if adjudication enabled */
6127                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6128                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6129                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6130                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6131                             return;
6132                         }
6133                   }
6134                 } else bare = 1;
6135
6136
6137             // don't wait for engine to announce game end if we can judge ourselves
6138             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6139                                        castlingRights[forwardMostMove]) ) {
6140               case MT_CHECK:
6141                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6142                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6143                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6144                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6145                             checkCnt++;
6146                         if(checkCnt >= 2) {
6147                             reason = "Xboard adjudication: 3rd check";
6148                             epStatus[forwardMostMove] = EP_CHECKMATE;
6149                             break;
6150                         }
6151                     }
6152                 }
6153               case MT_NONE:
6154               default:
6155                 break;
6156               case MT_STALEMATE:
6157               case MT_STAINMATE:
6158                 reason = "Xboard adjudication: Stalemate";
6159                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6160                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6161                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6162                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6163                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6164                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6165                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6166                                                                         EP_CHECKMATE : EP_WINS);
6167                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6168                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6169                 }
6170                 break;
6171               case MT_CHECKMATE:
6172                 reason = "Xboard adjudication: Checkmate";
6173                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6174                 break;
6175             }
6176
6177                 switch(i = epStatus[forwardMostMove]) {
6178                     case EP_STALEMATE:
6179                         result = GameIsDrawn; break;
6180                     case EP_CHECKMATE:
6181                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6182                     case EP_WINS:
6183                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6184                     default:
6185                         result = (ChessMove) 0;
6186                 }
6187                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6188                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6189                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6190                     GameEnds( result, reason, GE_XBOARD );
6191                     return;
6192                 }
6193
6194                 /* Next absolutely insufficient mating material. */
6195                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6196                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6197                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6198                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6199                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6200
6201                      /* always flag draws, for judging claims */
6202                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6203
6204                      if(appData.materialDraws) {
6205                          /* but only adjudicate them if adjudication enabled */
6206                          SendToProgram("force\n", cps->other); // suppress reply
6207                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6208                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6209                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6210                          return;
6211                      }
6212                 }
6213
6214                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6215                 if(NrPieces == 4 && 
6216                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6217                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6218                    || NrWN==2 || NrBN==2     /* KNNK */
6219                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6220                   ) ) {
6221                      if(--moveCount < 0 && appData.trivialDraws)
6222                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6223                           SendToProgram("force\n", cps->other); // suppress reply
6224                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6225                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6226                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6227                           return;
6228                      }
6229                 } else moveCount = 6;
6230             }
6231           }
6232
6233                 /* Check for rep-draws */
6234                 count = 0;
6235                 for(k = forwardMostMove-2;
6236                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6237                         epStatus[k] < EP_UNKNOWN &&
6238                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6239                     k-=2)
6240                 {   int rights=0;
6241                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6242                         /* compare castling rights */
6243                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6244                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6245                                 rights++; /* King lost rights, while rook still had them */
6246                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6247                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6248                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6249                                    rights++; /* but at least one rook lost them */
6250                         }
6251                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6252                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6253                                 rights++; 
6254                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6255                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6256                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6257                                    rights++;
6258                         }
6259                         if( rights == 0 && ++count > appData.drawRepeats-2
6260                             && appData.drawRepeats > 1) {
6261                              /* adjudicate after user-specified nr of repeats */
6262                              SendToProgram("force\n", cps->other); // suppress reply
6263                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6264                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6265                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6266                                 // [HGM] xiangqi: check for forbidden perpetuals
6267                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6268                                 for(m=forwardMostMove; m>k; m-=2) {
6269                                     if(MateTest(boards[m], PosFlags(m), 
6270                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6271                                         ourPerpetual = 0; // the current mover did not always check
6272                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6273                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6274                                         hisPerpetual = 0; // the opponent did not always check
6275                                 }
6276                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6277                                                                         ourPerpetual, hisPerpetual);
6278                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6279                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6280                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6281                                     return;
6282                                 }
6283                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6284                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6285                                 // Now check for perpetual chases
6286                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6287                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6288                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6289                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6290                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6291                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6292                                         return;
6293                                     }
6294                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6295                                         break; // Abort repetition-checking loop.
6296                                 }
6297                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6298                              }
6299                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6300                              return;
6301                         }
6302                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6303                              epStatus[forwardMostMove] = EP_REP_DRAW;
6304                     }
6305                 }
6306
6307                 /* Now we test for 50-move draws. Determine ply count */
6308                 count = forwardMostMove;
6309                 /* look for last irreversble move */
6310                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6311                     count--;
6312                 /* if we hit starting position, add initial plies */
6313                 if( count == backwardMostMove )
6314                     count -= initialRulePlies;
6315                 count = forwardMostMove - count; 
6316                 if( count >= 100)
6317                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6318                          /* this is used to judge if draw claims are legal */
6319                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6320                          SendToProgram("force\n", cps->other); // suppress reply
6321                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6322                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6323                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6324                          return;
6325                 }
6326
6327                 /* if draw offer is pending, treat it as a draw claim
6328                  * when draw condition present, to allow engines a way to
6329                  * claim draws before making their move to avoid a race
6330                  * condition occurring after their move
6331                  */
6332                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6333                          char *p = NULL;
6334                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6335                              p = "Draw claim: 50-move rule";
6336                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6337                              p = "Draw claim: 3-fold repetition";
6338                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6339                              p = "Draw claim: insufficient mating material";
6340                          if( p != NULL ) {
6341                              SendToProgram("force\n", cps->other); // suppress reply
6342                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6343                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6344                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6345                              return;
6346                          }
6347                 }
6348
6349
6350                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6351                     SendToProgram("force\n", cps->other); // suppress reply
6352                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6353                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6354
6355                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6356
6357                     return;
6358                 }
6359         }
6360
6361         bookHit = NULL;
6362         if (gameMode == TwoMachinesPlay) {
6363             /* [HGM] relaying draw offers moved to after reception of move */
6364             /* and interpreting offer as claim if it brings draw condition */
6365             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6366                 SendToProgram("draw\n", cps->other);
6367             }
6368             if (cps->other->sendTime) {
6369                 SendTimeRemaining(cps->other,
6370                                   cps->other->twoMachinesColor[0] == 'w');
6371             }
6372             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6373             if (firstMove && !bookHit) {
6374                 firstMove = FALSE;
6375                 if (cps->other->useColors) {
6376                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6377                 }
6378                 SendToProgram("go\n", cps->other);
6379             }
6380             cps->other->maybeThinking = TRUE;
6381         }
6382
6383         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6384         
6385         if (!pausing && appData.ringBellAfterMoves) {
6386             RingBell();
6387         }
6388
6389         /* 
6390          * Reenable menu items that were disabled while
6391          * machine was thinking
6392          */
6393         if (gameMode != TwoMachinesPlay)
6394             SetUserThinkingEnables();
6395
6396         // [HGM] book: after book hit opponent has received move and is now in force mode
6397         // force the book reply into it, and then fake that it outputted this move by jumping
6398         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6399         if(bookHit) {
6400                 static char bookMove[MSG_SIZ]; // a bit generous?
6401
6402                 strcpy(bookMove, "move ");
6403                 strcat(bookMove, bookHit);
6404                 message = bookMove;
6405                 cps = cps->other;
6406                 programStats.nodes = programStats.depth = programStats.time = 
6407                 programStats.score = programStats.got_only_move = 0;
6408                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6409
6410                 if(cps->lastPing != cps->lastPong) {
6411                     savedMessage = message; // args for deferred call
6412                     savedState = cps;
6413                     ScheduleDelayedEvent(DeferredBookMove, 10);
6414                     return;
6415                 }
6416                 goto FakeBookMove;
6417         }
6418
6419         return;
6420     }
6421
6422     /* Set special modes for chess engines.  Later something general
6423      *  could be added here; for now there is just one kludge feature,
6424      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6425      *  when "xboard" is given as an interactive command.
6426      */
6427     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6428         cps->useSigint = FALSE;
6429         cps->useSigterm = FALSE;
6430     }
6431     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6432       ParseFeatures(message+8, cps);
6433       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6434     }
6435
6436     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6437      * want this, I was asked to put it in, and obliged.
6438      */
6439     if (!strncmp(message, "setboard ", 9)) {
6440         Board initial_position; int i;
6441
6442         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6443
6444         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6445             DisplayError(_("Bad FEN received from engine"), 0);
6446             return ;
6447         } else {
6448            Reset(TRUE, FALSE);
6449            CopyBoard(boards[0], initial_position);
6450            initialRulePlies = FENrulePlies;
6451            epStatus[0] = FENepStatus;
6452            for( i=0; i<nrCastlingRights; i++ )
6453                 castlingRights[0][i] = FENcastlingRights[i];
6454            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6455            else gameMode = MachinePlaysBlack;                 
6456            DrawPosition(FALSE, boards[currentMove]);
6457         }
6458         return;
6459     }
6460
6461     /*
6462      * Look for communication commands
6463      */
6464     if (!strncmp(message, "telluser ", 9)) {
6465         DisplayNote(message + 9);
6466         return;
6467     }
6468     if (!strncmp(message, "tellusererror ", 14)) {
6469         DisplayError(message + 14, 0);
6470         return;
6471     }
6472     if (!strncmp(message, "tellopponent ", 13)) {
6473       if (appData.icsActive) {
6474         if (loggedOn) {
6475           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6476           SendToICS(buf1);
6477         }
6478       } else {
6479         DisplayNote(message + 13);
6480       }
6481       return;
6482     }
6483     if (!strncmp(message, "tellothers ", 11)) {
6484       if (appData.icsActive) {
6485         if (loggedOn) {
6486           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6487           SendToICS(buf1);
6488         }
6489       }
6490       return;
6491     }
6492     if (!strncmp(message, "tellall ", 8)) {
6493       if (appData.icsActive) {
6494         if (loggedOn) {
6495           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6496           SendToICS(buf1);
6497         }
6498       } else {
6499         DisplayNote(message + 8);
6500       }
6501       return;
6502     }
6503     if (strncmp(message, "warning", 7) == 0) {
6504         /* Undocumented feature, use tellusererror in new code */
6505         DisplayError(message, 0);
6506         return;
6507     }
6508     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6509         strcpy(realname, cps->tidy);
6510         strcat(realname, " query");
6511         AskQuestion(realname, buf2, buf1, cps->pr);
6512         return;
6513     }
6514     /* Commands from the engine directly to ICS.  We don't allow these to be 
6515      *  sent until we are logged on. Crafty kibitzes have been known to 
6516      *  interfere with the login process.
6517      */
6518     if (loggedOn) {
6519         if (!strncmp(message, "tellics ", 8)) {
6520             SendToICS(message + 8);
6521             SendToICS("\n");
6522             return;
6523         }
6524         if (!strncmp(message, "tellicsnoalias ", 15)) {
6525             SendToICS(ics_prefix);
6526             SendToICS(message + 15);
6527             SendToICS("\n");
6528             return;
6529         }
6530         /* The following are for backward compatibility only */
6531         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6532             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6533             SendToICS(ics_prefix);
6534             SendToICS(message);
6535             SendToICS("\n");
6536             return;
6537         }
6538     }
6539     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6540         return;
6541     }
6542     /*
6543      * If the move is illegal, cancel it and redraw the board.
6544      * Also deal with other error cases.  Matching is rather loose
6545      * here to accommodate engines written before the spec.
6546      */
6547     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6548         strncmp(message, "Error", 5) == 0) {
6549         if (StrStr(message, "name") || 
6550             StrStr(message, "rating") || StrStr(message, "?") ||
6551             StrStr(message, "result") || StrStr(message, "board") ||
6552             StrStr(message, "bk") || StrStr(message, "computer") ||
6553             StrStr(message, "variant") || StrStr(message, "hint") ||
6554             StrStr(message, "random") || StrStr(message, "depth") ||
6555             StrStr(message, "accepted")) {
6556             return;
6557         }
6558         if (StrStr(message, "protover")) {
6559           /* Program is responding to input, so it's apparently done
6560              initializing, and this error message indicates it is
6561              protocol version 1.  So we don't need to wait any longer
6562              for it to initialize and send feature commands. */
6563           FeatureDone(cps, 1);
6564           cps->protocolVersion = 1;
6565           return;
6566         }
6567         cps->maybeThinking = FALSE;
6568
6569         if (StrStr(message, "draw")) {
6570             /* Program doesn't have "draw" command */
6571             cps->sendDrawOffers = 0;
6572             return;
6573         }
6574         if (cps->sendTime != 1 &&
6575             (StrStr(message, "time") || StrStr(message, "otim"))) {
6576           /* Program apparently doesn't have "time" or "otim" command */
6577           cps->sendTime = 0;
6578           return;
6579         }
6580         if (StrStr(message, "analyze")) {
6581             cps->analysisSupport = FALSE;
6582             cps->analyzing = FALSE;
6583             Reset(FALSE, TRUE);
6584             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6585             DisplayError(buf2, 0);
6586             return;
6587         }
6588         if (StrStr(message, "(no matching move)st")) {
6589           /* Special kludge for GNU Chess 4 only */
6590           cps->stKludge = TRUE;
6591           SendTimeControl(cps, movesPerSession, timeControl,
6592                           timeIncrement, appData.searchDepth,
6593                           searchTime);
6594           return;
6595         }
6596         if (StrStr(message, "(no matching move)sd")) {
6597           /* Special kludge for GNU Chess 4 only */
6598           cps->sdKludge = TRUE;
6599           SendTimeControl(cps, movesPerSession, timeControl,
6600                           timeIncrement, appData.searchDepth,
6601                           searchTime);
6602           return;
6603         }
6604         if (!StrStr(message, "llegal")) {
6605             return;
6606         }
6607         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6608             gameMode == IcsIdle) return;
6609         if (forwardMostMove <= backwardMostMove) return;
6610         if (pausing) PauseEvent();
6611       if(appData.forceIllegal) {
6612             // [HGM] illegal: machine refused move; force position after move into it
6613           SendToProgram("force\n", cps);
6614           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6615                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6616                 // when black is to move, while there might be nothing on a2 or black
6617                 // might already have the move. So send the board as if white has the move.
6618                 // But first we must change the stm of the engine, as it refused the last move
6619                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6620                 if(WhiteOnMove(forwardMostMove)) {
6621                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6622                     SendBoard(cps, forwardMostMove); // kludgeless board
6623                 } else {
6624                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6625                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6626                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6627                 }
6628           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6629             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6630                  gameMode == TwoMachinesPlay)
6631               SendToProgram("go\n", cps);
6632             return;
6633       } else
6634         if (gameMode == PlayFromGameFile) {
6635             /* Stop reading this game file */
6636             gameMode = EditGame;
6637             ModeHighlight();
6638         }
6639         currentMove = --forwardMostMove;
6640         DisplayMove(currentMove-1); /* before DisplayMoveError */
6641         SwitchClocks();
6642         DisplayBothClocks();
6643         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6644                 parseList[currentMove], cps->which);
6645         DisplayMoveError(buf1);
6646         DrawPosition(FALSE, boards[currentMove]);
6647
6648         /* [HGM] illegal-move claim should forfeit game when Xboard */
6649         /* only passes fully legal moves                            */
6650         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6651             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6652                                 "False illegal-move claim", GE_XBOARD );
6653         }
6654         return;
6655     }
6656     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6657         /* Program has a broken "time" command that
6658            outputs a string not ending in newline.
6659            Don't use it. */
6660         cps->sendTime = 0;
6661     }
6662     
6663     /*
6664      * If chess program startup fails, exit with an error message.
6665      * Attempts to recover here are futile.
6666      */
6667     if ((StrStr(message, "unknown host") != NULL)
6668         || (StrStr(message, "No remote directory") != NULL)
6669         || (StrStr(message, "not found") != NULL)
6670         || (StrStr(message, "No such file") != NULL)
6671         || (StrStr(message, "can't alloc") != NULL)
6672         || (StrStr(message, "Permission denied") != NULL)) {
6673
6674         cps->maybeThinking = FALSE;
6675         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6676                 cps->which, cps->program, cps->host, message);
6677         RemoveInputSource(cps->isr);
6678         DisplayFatalError(buf1, 0, 1);
6679         return;
6680     }
6681     
6682     /* 
6683      * Look for hint output
6684      */
6685     if (sscanf(message, "Hint: %s", buf1) == 1) {
6686         if (cps == &first && hintRequested) {
6687             hintRequested = FALSE;
6688             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6689                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6690                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6691                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6692                                     fromY, fromX, toY, toX, promoChar, buf1);
6693                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6694                 DisplayInformation(buf2);
6695             } else {
6696                 /* Hint move could not be parsed!? */
6697               snprintf(buf2, sizeof(buf2),
6698                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6699                         buf1, cps->which);
6700                 DisplayError(buf2, 0);
6701             }
6702         } else {
6703             strcpy(lastHint, buf1);
6704         }
6705         return;
6706     }
6707
6708     /*
6709      * Ignore other messages if game is not in progress
6710      */
6711     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6712         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6713
6714     /*
6715      * look for win, lose, draw, or draw offer
6716      */
6717     if (strncmp(message, "1-0", 3) == 0) {
6718         char *p, *q, *r = "";
6719         p = strchr(message, '{');
6720         if (p) {
6721             q = strchr(p, '}');
6722             if (q) {
6723                 *q = NULLCHAR;
6724                 r = p + 1;
6725             }
6726         }
6727         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6728         return;
6729     } else if (strncmp(message, "0-1", 3) == 0) {
6730         char *p, *q, *r = "";
6731         p = strchr(message, '{');
6732         if (p) {
6733             q = strchr(p, '}');
6734             if (q) {
6735                 *q = NULLCHAR;
6736                 r = p + 1;
6737             }
6738         }
6739         /* Kludge for Arasan 4.1 bug */
6740         if (strcmp(r, "Black resigns") == 0) {
6741             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6742             return;
6743         }
6744         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6745         return;
6746     } else if (strncmp(message, "1/2", 3) == 0) {
6747         char *p, *q, *r = "";
6748         p = strchr(message, '{');
6749         if (p) {
6750             q = strchr(p, '}');
6751             if (q) {
6752                 *q = NULLCHAR;
6753                 r = p + 1;
6754             }
6755         }
6756             
6757         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6758         return;
6759
6760     } else if (strncmp(message, "White resign", 12) == 0) {
6761         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6762         return;
6763     } else if (strncmp(message, "Black resign", 12) == 0) {
6764         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6765         return;
6766     } else if (strncmp(message, "White matches", 13) == 0 ||
6767                strncmp(message, "Black matches", 13) == 0   ) {
6768         /* [HGM] ignore GNUShogi noises */
6769         return;
6770     } else if (strncmp(message, "White", 5) == 0 &&
6771                message[5] != '(' &&
6772                StrStr(message, "Black") == NULL) {
6773         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6774         return;
6775     } else if (strncmp(message, "Black", 5) == 0 &&
6776                message[5] != '(') {
6777         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6778         return;
6779     } else if (strcmp(message, "resign") == 0 ||
6780                strcmp(message, "computer resigns") == 0) {
6781         switch (gameMode) {
6782           case MachinePlaysBlack:
6783           case IcsPlayingBlack:
6784             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6785             break;
6786           case MachinePlaysWhite:
6787           case IcsPlayingWhite:
6788             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6789             break;
6790           case TwoMachinesPlay:
6791             if (cps->twoMachinesColor[0] == 'w')
6792               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6793             else
6794               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6795             break;
6796           default:
6797             /* can't happen */
6798             break;
6799         }
6800         return;
6801     } else if (strncmp(message, "opponent mates", 14) == 0) {
6802         switch (gameMode) {
6803           case MachinePlaysBlack:
6804           case IcsPlayingBlack:
6805             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6806             break;
6807           case MachinePlaysWhite:
6808           case IcsPlayingWhite:
6809             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6810             break;
6811           case TwoMachinesPlay:
6812             if (cps->twoMachinesColor[0] == 'w')
6813               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6814             else
6815               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6816             break;
6817           default:
6818             /* can't happen */
6819             break;
6820         }
6821         return;
6822     } else if (strncmp(message, "computer mates", 14) == 0) {
6823         switch (gameMode) {
6824           case MachinePlaysBlack:
6825           case IcsPlayingBlack:
6826             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6827             break;
6828           case MachinePlaysWhite:
6829           case IcsPlayingWhite:
6830             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6831             break;
6832           case TwoMachinesPlay:
6833             if (cps->twoMachinesColor[0] == 'w')
6834               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6835             else
6836               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6837             break;
6838           default:
6839             /* can't happen */
6840             break;
6841         }
6842         return;
6843     } else if (strncmp(message, "checkmate", 9) == 0) {
6844         if (WhiteOnMove(forwardMostMove)) {
6845             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6846         } else {
6847             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6848         }
6849         return;
6850     } else if (strstr(message, "Draw") != NULL ||
6851                strstr(message, "game is a draw") != NULL) {
6852         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6853         return;
6854     } else if (strstr(message, "offer") != NULL &&
6855                strstr(message, "draw") != NULL) {
6856 #if ZIPPY
6857         if (appData.zippyPlay && first.initDone) {
6858             /* Relay offer to ICS */
6859             SendToICS(ics_prefix);
6860             SendToICS("draw\n");
6861         }
6862 #endif
6863         cps->offeredDraw = 2; /* valid until this engine moves twice */
6864         if (gameMode == TwoMachinesPlay) {
6865             if (cps->other->offeredDraw) {
6866                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6867             /* [HGM] in two-machine mode we delay relaying draw offer      */
6868             /* until after we also have move, to see if it is really claim */
6869             }
6870         } else if (gameMode == MachinePlaysWhite ||
6871                    gameMode == MachinePlaysBlack) {
6872           if (userOfferedDraw) {
6873             DisplayInformation(_("Machine accepts your draw offer"));
6874             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6875           } else {
6876             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6877           }
6878         }
6879     }
6880
6881     
6882     /*
6883      * Look for thinking output
6884      */
6885     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6886           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6887                                 ) {
6888         int plylev, mvleft, mvtot, curscore, time;
6889         char mvname[MOVE_LEN];
6890         u64 nodes; // [DM]
6891         char plyext;
6892         int ignore = FALSE;
6893         int prefixHint = FALSE;
6894         mvname[0] = NULLCHAR;
6895
6896         switch (gameMode) {
6897           case MachinePlaysBlack:
6898           case IcsPlayingBlack:
6899             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6900             break;
6901           case MachinePlaysWhite:
6902           case IcsPlayingWhite:
6903             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6904             break;
6905           case AnalyzeMode:
6906           case AnalyzeFile:
6907             break;
6908           case IcsObserving: /* [DM] icsEngineAnalyze */
6909             if (!appData.icsEngineAnalyze) ignore = TRUE;
6910             break;
6911           case TwoMachinesPlay:
6912             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6913                 ignore = TRUE;
6914             }
6915             break;
6916           default:
6917             ignore = TRUE;
6918             break;
6919         }
6920
6921         if (!ignore) {
6922             buf1[0] = NULLCHAR;
6923             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6924                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6925
6926                 if (plyext != ' ' && plyext != '\t') {
6927                     time *= 100;
6928                 }
6929
6930                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6931                 if( cps->scoreIsAbsolute && 
6932                     ( gameMode == MachinePlaysBlack ||
6933                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6934                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6935                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6936                      !WhiteOnMove(currentMove)
6937                     ) )
6938                 {
6939                     curscore = -curscore;
6940                 }
6941
6942
6943                 programStats.depth = plylev;
6944                 programStats.nodes = nodes;
6945                 programStats.time = time;
6946                 programStats.score = curscore;
6947                 programStats.got_only_move = 0;
6948
6949                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6950                         int ticklen;
6951
6952                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6953                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6954                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6955                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6956                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6957                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6958                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6959                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6960                 }
6961
6962                 /* Buffer overflow protection */
6963                 if (buf1[0] != NULLCHAR) {
6964                     if (strlen(buf1) >= sizeof(programStats.movelist)
6965                         && appData.debugMode) {
6966                         fprintf(debugFP,
6967                                 "PV is too long; using the first %u bytes.\n",
6968                                 (unsigned) sizeof(programStats.movelist) - 1);
6969                     }
6970
6971                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6972                 } else {
6973                     sprintf(programStats.movelist, " no PV\n");
6974                 }
6975
6976                 if (programStats.seen_stat) {
6977                     programStats.ok_to_send = 1;
6978                 }
6979
6980                 if (strchr(programStats.movelist, '(') != NULL) {
6981                     programStats.line_is_book = 1;
6982                     programStats.nr_moves = 0;
6983                     programStats.moves_left = 0;
6984                 } else {
6985                     programStats.line_is_book = 0;
6986                 }
6987
6988                 SendProgramStatsToFrontend( cps, &programStats );
6989
6990                 /* 
6991                     [AS] Protect the thinkOutput buffer from overflow... this
6992                     is only useful if buf1 hasn't overflowed first!
6993                 */
6994                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6995                         plylev, 
6996                         (gameMode == TwoMachinesPlay ?
6997                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6998                         ((double) curscore) / 100.0,
6999                         prefixHint ? lastHint : "",
7000                         prefixHint ? " " : "" );
7001
7002                 if( buf1[0] != NULLCHAR ) {
7003                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7004
7005                     if( strlen(buf1) > max_len ) {
7006                         if( appData.debugMode) {
7007                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7008                         }
7009                         buf1[max_len+1] = '\0';
7010                     }
7011
7012                     strcat( thinkOutput, buf1 );
7013                 }
7014
7015                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7016                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7017                     DisplayMove(currentMove - 1);
7018                 }
7019                 return;
7020
7021             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7022                 /* crafty (9.25+) says "(only move) <move>"
7023                  * if there is only 1 legal move
7024                  */
7025                 sscanf(p, "(only move) %s", buf1);
7026                 sprintf(thinkOutput, "%s (only move)", buf1);
7027                 sprintf(programStats.movelist, "%s (only move)", buf1);
7028                 programStats.depth = 1;
7029                 programStats.nr_moves = 1;
7030                 programStats.moves_left = 1;
7031                 programStats.nodes = 1;
7032                 programStats.time = 1;
7033                 programStats.got_only_move = 1;
7034
7035                 /* Not really, but we also use this member to
7036                    mean "line isn't going to change" (Crafty
7037                    isn't searching, so stats won't change) */
7038                 programStats.line_is_book = 1;
7039
7040                 SendProgramStatsToFrontend( cps, &programStats );
7041                 
7042                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7043                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7044                     DisplayMove(currentMove - 1);
7045                 }
7046                 return;
7047             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7048                               &time, &nodes, &plylev, &mvleft,
7049                               &mvtot, mvname) >= 5) {
7050                 /* The stat01: line is from Crafty (9.29+) in response
7051                    to the "." command */
7052                 programStats.seen_stat = 1;
7053                 cps->maybeThinking = TRUE;
7054
7055                 if (programStats.got_only_move || !appData.periodicUpdates)
7056                   return;
7057
7058                 programStats.depth = plylev;
7059                 programStats.time = time;
7060                 programStats.nodes = nodes;
7061                 programStats.moves_left = mvleft;
7062                 programStats.nr_moves = mvtot;
7063                 strcpy(programStats.move_name, mvname);
7064                 programStats.ok_to_send = 1;
7065                 programStats.movelist[0] = '\0';
7066
7067                 SendProgramStatsToFrontend( cps, &programStats );
7068
7069                 return;
7070
7071             } else if (strncmp(message,"++",2) == 0) {
7072                 /* Crafty 9.29+ outputs this */
7073                 programStats.got_fail = 2;
7074                 return;
7075
7076             } else if (strncmp(message,"--",2) == 0) {
7077                 /* Crafty 9.29+ outputs this */
7078                 programStats.got_fail = 1;
7079                 return;
7080
7081             } else if (thinkOutput[0] != NULLCHAR &&
7082                        strncmp(message, "    ", 4) == 0) {
7083                 unsigned message_len;
7084
7085                 p = message;
7086                 while (*p && *p == ' ') p++;
7087
7088                 message_len = strlen( p );
7089
7090                 /* [AS] Avoid buffer overflow */
7091                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7092                     strcat(thinkOutput, " ");
7093                     strcat(thinkOutput, p);
7094                 }
7095
7096                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7097                     strcat(programStats.movelist, " ");
7098                     strcat(programStats.movelist, p);
7099                 }
7100
7101                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7102                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7103                     DisplayMove(currentMove - 1);
7104                 }
7105                 return;
7106             }
7107         }
7108         else {
7109             buf1[0] = NULLCHAR;
7110
7111             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7112                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7113             {
7114                 ChessProgramStats cpstats;
7115
7116                 if (plyext != ' ' && plyext != '\t') {
7117                     time *= 100;
7118                 }
7119
7120                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7121                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7122                     curscore = -curscore;
7123                 }
7124
7125                 cpstats.depth = plylev;
7126                 cpstats.nodes = nodes;
7127                 cpstats.time = time;
7128                 cpstats.score = curscore;
7129                 cpstats.got_only_move = 0;
7130                 cpstats.movelist[0] = '\0';
7131
7132                 if (buf1[0] != NULLCHAR) {
7133                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7134                 }
7135
7136                 cpstats.ok_to_send = 0;
7137                 cpstats.line_is_book = 0;
7138                 cpstats.nr_moves = 0;
7139                 cpstats.moves_left = 0;
7140
7141                 SendProgramStatsToFrontend( cps, &cpstats );
7142             }
7143         }
7144     }
7145 }
7146
7147
7148 /* Parse a game score from the character string "game", and
7149    record it as the history of the current game.  The game
7150    score is NOT assumed to start from the standard position. 
7151    The display is not updated in any way.
7152    */
7153 void
7154 ParseGameHistory(game)
7155      char *game;
7156 {
7157     ChessMove moveType;
7158     int fromX, fromY, toX, toY, boardIndex;
7159     char promoChar;
7160     char *p, *q;
7161     char buf[MSG_SIZ];
7162
7163     if (appData.debugMode)
7164       fprintf(debugFP, "Parsing game history: %s\n", game);
7165
7166     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7167     gameInfo.site = StrSave(appData.icsHost);
7168     gameInfo.date = PGNDate();
7169     gameInfo.round = StrSave("-");
7170
7171     /* Parse out names of players */
7172     while (*game == ' ') game++;
7173     p = buf;
7174     while (*game != ' ') *p++ = *game++;
7175     *p = NULLCHAR;
7176     gameInfo.white = StrSave(buf);
7177     while (*game == ' ') game++;
7178     p = buf;
7179     while (*game != ' ' && *game != '\n') *p++ = *game++;
7180     *p = NULLCHAR;
7181     gameInfo.black = StrSave(buf);
7182
7183     /* Parse moves */
7184     boardIndex = blackPlaysFirst ? 1 : 0;
7185     yynewstr(game);
7186     for (;;) {
7187         yyboardindex = boardIndex;
7188         moveType = (ChessMove) yylex();
7189         switch (moveType) {
7190           case IllegalMove:             /* maybe suicide chess, etc. */
7191   if (appData.debugMode) {
7192     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7193     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7194     setbuf(debugFP, NULL);
7195   }
7196           case WhitePromotionChancellor:
7197           case BlackPromotionChancellor:
7198           case WhitePromotionArchbishop:
7199           case BlackPromotionArchbishop:
7200           case WhitePromotionQueen:
7201           case BlackPromotionQueen:
7202           case WhitePromotionRook:
7203           case BlackPromotionRook:
7204           case WhitePromotionBishop:
7205           case BlackPromotionBishop:
7206           case WhitePromotionKnight:
7207           case BlackPromotionKnight:
7208           case WhitePromotionKing:
7209           case BlackPromotionKing:
7210           case NormalMove:
7211           case WhiteCapturesEnPassant:
7212           case BlackCapturesEnPassant:
7213           case WhiteKingSideCastle:
7214           case WhiteQueenSideCastle:
7215           case BlackKingSideCastle:
7216           case BlackQueenSideCastle:
7217           case WhiteKingSideCastleWild:
7218           case WhiteQueenSideCastleWild:
7219           case BlackKingSideCastleWild:
7220           case BlackQueenSideCastleWild:
7221           /* PUSH Fabien */
7222           case WhiteHSideCastleFR:
7223           case WhiteASideCastleFR:
7224           case BlackHSideCastleFR:
7225           case BlackASideCastleFR:
7226           /* POP Fabien */
7227             fromX = currentMoveString[0] - AAA;
7228             fromY = currentMoveString[1] - ONE;
7229             toX = currentMoveString[2] - AAA;
7230             toY = currentMoveString[3] - ONE;
7231             promoChar = currentMoveString[4];
7232             break;
7233           case WhiteDrop:
7234           case BlackDrop:
7235             fromX = moveType == WhiteDrop ?
7236               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7237             (int) CharToPiece(ToLower(currentMoveString[0]));
7238             fromY = DROP_RANK;
7239             toX = currentMoveString[2] - AAA;
7240             toY = currentMoveString[3] - ONE;
7241             promoChar = NULLCHAR;
7242             break;
7243           case AmbiguousMove:
7244             /* bug? */
7245             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7246   if (appData.debugMode) {
7247     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7248     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7249     setbuf(debugFP, NULL);
7250   }
7251             DisplayError(buf, 0);
7252             return;
7253           case ImpossibleMove:
7254             /* bug? */
7255             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7256   if (appData.debugMode) {
7257     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7259     setbuf(debugFP, NULL);
7260   }
7261             DisplayError(buf, 0);
7262             return;
7263           case (ChessMove) 0:   /* end of file */
7264             if (boardIndex < backwardMostMove) {
7265                 /* Oops, gap.  How did that happen? */
7266                 DisplayError(_("Gap in move list"), 0);
7267                 return;
7268             }
7269             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7270             if (boardIndex > forwardMostMove) {
7271                 forwardMostMove = boardIndex;
7272             }
7273             return;
7274           case ElapsedTime:
7275             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7276                 strcat(parseList[boardIndex-1], " ");
7277                 strcat(parseList[boardIndex-1], yy_text);
7278             }
7279             continue;
7280           case Comment:
7281           case PGNTag:
7282           case NAG:
7283           default:
7284             /* ignore */
7285             continue;
7286           case WhiteWins:
7287           case BlackWins:
7288           case GameIsDrawn:
7289           case GameUnfinished:
7290             if (gameMode == IcsExamining) {
7291                 if (boardIndex < backwardMostMove) {
7292                     /* Oops, gap.  How did that happen? */
7293                     return;
7294                 }
7295                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7296                 return;
7297             }
7298             gameInfo.result = moveType;
7299             p = strchr(yy_text, '{');
7300             if (p == NULL) p = strchr(yy_text, '(');
7301             if (p == NULL) {
7302                 p = yy_text;
7303                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7304             } else {
7305                 q = strchr(p, *p == '{' ? '}' : ')');
7306                 if (q != NULL) *q = NULLCHAR;
7307                 p++;
7308             }
7309             gameInfo.resultDetails = StrSave(p);
7310             continue;
7311         }
7312         if (boardIndex >= forwardMostMove &&
7313             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7314             backwardMostMove = blackPlaysFirst ? 1 : 0;
7315             return;
7316         }
7317         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7318                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7319                                  parseList[boardIndex]);
7320         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7321         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7322         /* currentMoveString is set as a side-effect of yylex */
7323         strcpy(moveList[boardIndex], currentMoveString);
7324         strcat(moveList[boardIndex], "\n");
7325         boardIndex++;
7326         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7327                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7328         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7329                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7330           case MT_NONE:
7331           case MT_STALEMATE:
7332           default:
7333             break;
7334           case MT_CHECK:
7335             if(gameInfo.variant != VariantShogi)
7336                 strcat(parseList[boardIndex - 1], "+");
7337             break;
7338           case MT_CHECKMATE:
7339           case MT_STAINMATE:
7340             strcat(parseList[boardIndex - 1], "#");
7341             break;
7342         }
7343     }
7344 }
7345
7346
7347 /* Apply a move to the given board  */
7348 void
7349 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7350      int fromX, fromY, toX, toY;
7351      int promoChar;
7352      Board board;
7353      char *castling;
7354      char *ep;
7355 {
7356   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7357
7358     /* [HGM] compute & store e.p. status and castling rights for new position */
7359     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7360     { int i;
7361
7362       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7363       oldEP = *ep;
7364       *ep = EP_NONE;
7365
7366       if( board[toY][toX] != EmptySquare ) 
7367            *ep = EP_CAPTURE;  
7368
7369       if( board[fromY][fromX] == WhitePawn ) {
7370            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7371                *ep = EP_PAWN_MOVE;
7372            if( toY-fromY==2) {
7373                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7374                         gameInfo.variant != VariantBerolina || toX < fromX)
7375                       *ep = toX | berolina;
7376                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7377                         gameInfo.variant != VariantBerolina || toX > fromX) 
7378                       *ep = toX;
7379            }
7380       } else 
7381       if( board[fromY][fromX] == BlackPawn ) {
7382            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7383                *ep = EP_PAWN_MOVE; 
7384            if( toY-fromY== -2) {
7385                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7386                         gameInfo.variant != VariantBerolina || toX < fromX)
7387                       *ep = toX | berolina;
7388                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7389                         gameInfo.variant != VariantBerolina || toX > fromX) 
7390                       *ep = toX;
7391            }
7392        }
7393
7394        for(i=0; i<nrCastlingRights; i++) {
7395            if(castling[i] == fromX && castlingRank[i] == fromY ||
7396               castling[i] == toX   && castlingRank[i] == toY   
7397              ) castling[i] = -1; // revoke for moved or captured piece
7398        }
7399
7400     }
7401
7402   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7403   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7404        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7405          
7406   if (fromX == toX && fromY == toY) return;
7407
7408   if (fromY == DROP_RANK) {
7409         /* must be first */
7410         piece = board[toY][toX] = (ChessSquare) fromX;
7411   } else {
7412      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7413      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7414      if(gameInfo.variant == VariantKnightmate)
7415          king += (int) WhiteUnicorn - (int) WhiteKing;
7416
7417     /* Code added by Tord: */
7418     /* FRC castling assumed when king captures friendly rook. */
7419     if (board[fromY][fromX] == WhiteKing &&
7420              board[toY][toX] == WhiteRook) {
7421       board[fromY][fromX] = EmptySquare;
7422       board[toY][toX] = EmptySquare;
7423       if(toX > fromX) {
7424         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7425       } else {
7426         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7427       }
7428     } else if (board[fromY][fromX] == BlackKing &&
7429                board[toY][toX] == BlackRook) {
7430       board[fromY][fromX] = EmptySquare;
7431       board[toY][toX] = EmptySquare;
7432       if(toX > fromX) {
7433         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7434       } else {
7435         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7436       }
7437     /* End of code added by Tord */
7438
7439     } else if (board[fromY][fromX] == king
7440         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7441         && toY == fromY && toX > fromX+1) {
7442         board[fromY][fromX] = EmptySquare;
7443         board[toY][toX] = king;
7444         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7445         board[fromY][BOARD_RGHT-1] = EmptySquare;
7446     } else if (board[fromY][fromX] == king
7447         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7448                && toY == fromY && toX < fromX-1) {
7449         board[fromY][fromX] = EmptySquare;
7450         board[toY][toX] = king;
7451         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7452         board[fromY][BOARD_LEFT] = EmptySquare;
7453     } else if (board[fromY][fromX] == WhitePawn
7454                && toY == BOARD_HEIGHT-1
7455                && gameInfo.variant != VariantXiangqi
7456                ) {
7457         /* white pawn promotion */
7458         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7459         if (board[toY][toX] == EmptySquare) {
7460             board[toY][toX] = WhiteQueen;
7461         }
7462         if(gameInfo.variant==VariantBughouse ||
7463            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7464             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7465         board[fromY][fromX] = EmptySquare;
7466     } else if ((fromY == BOARD_HEIGHT-4)
7467                && (toX != fromX)
7468                && gameInfo.variant != VariantXiangqi
7469                && gameInfo.variant != VariantBerolina
7470                && (board[fromY][fromX] == WhitePawn)
7471                && (board[toY][toX] == EmptySquare)) {
7472         board[fromY][fromX] = EmptySquare;
7473         board[toY][toX] = WhitePawn;
7474         captured = board[toY - 1][toX];
7475         board[toY - 1][toX] = EmptySquare;
7476     } else if ((fromY == BOARD_HEIGHT-4)
7477                && (toX == fromX)
7478                && gameInfo.variant == VariantBerolina
7479                && (board[fromY][fromX] == WhitePawn)
7480                && (board[toY][toX] == EmptySquare)) {
7481         board[fromY][fromX] = EmptySquare;
7482         board[toY][toX] = WhitePawn;
7483         if(oldEP & EP_BEROLIN_A) {
7484                 captured = board[fromY][fromX-1];
7485                 board[fromY][fromX-1] = EmptySquare;
7486         }else{  captured = board[fromY][fromX+1];
7487                 board[fromY][fromX+1] = EmptySquare;
7488         }
7489     } else if (board[fromY][fromX] == king
7490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7491                && toY == fromY && toX > fromX+1) {
7492         board[fromY][fromX] = EmptySquare;
7493         board[toY][toX] = king;
7494         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7495         board[fromY][BOARD_RGHT-1] = EmptySquare;
7496     } else if (board[fromY][fromX] == king
7497         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7498                && toY == fromY && toX < fromX-1) {
7499         board[fromY][fromX] = EmptySquare;
7500         board[toY][toX] = king;
7501         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7502         board[fromY][BOARD_LEFT] = EmptySquare;
7503     } else if (fromY == 7 && fromX == 3
7504                && board[fromY][fromX] == BlackKing
7505                && toY == 7 && toX == 5) {
7506         board[fromY][fromX] = EmptySquare;
7507         board[toY][toX] = BlackKing;
7508         board[fromY][7] = EmptySquare;
7509         board[toY][4] = BlackRook;
7510     } else if (fromY == 7 && fromX == 3
7511                && board[fromY][fromX] == BlackKing
7512                && toY == 7 && toX == 1) {
7513         board[fromY][fromX] = EmptySquare;
7514         board[toY][toX] = BlackKing;
7515         board[fromY][0] = EmptySquare;
7516         board[toY][2] = BlackRook;
7517     } else if (board[fromY][fromX] == BlackPawn
7518                && toY == 0
7519                && gameInfo.variant != VariantXiangqi
7520                ) {
7521         /* black pawn promotion */
7522         board[0][toX] = CharToPiece(ToLower(promoChar));
7523         if (board[0][toX] == EmptySquare) {
7524             board[0][toX] = BlackQueen;
7525         }
7526         if(gameInfo.variant==VariantBughouse ||
7527            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7528             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7529         board[fromY][fromX] = EmptySquare;
7530     } else if ((fromY == 3)
7531                && (toX != fromX)
7532                && gameInfo.variant != VariantXiangqi
7533                && gameInfo.variant != VariantBerolina
7534                && (board[fromY][fromX] == BlackPawn)
7535                && (board[toY][toX] == EmptySquare)) {
7536         board[fromY][fromX] = EmptySquare;
7537         board[toY][toX] = BlackPawn;
7538         captured = board[toY + 1][toX];
7539         board[toY + 1][toX] = EmptySquare;
7540     } else if ((fromY == 3)
7541                && (toX == fromX)
7542                && gameInfo.variant == VariantBerolina
7543                && (board[fromY][fromX] == BlackPawn)
7544                && (board[toY][toX] == EmptySquare)) {
7545         board[fromY][fromX] = EmptySquare;
7546         board[toY][toX] = BlackPawn;
7547         if(oldEP & EP_BEROLIN_A) {
7548                 captured = board[fromY][fromX-1];
7549                 board[fromY][fromX-1] = EmptySquare;
7550         }else{  captured = board[fromY][fromX+1];
7551                 board[fromY][fromX+1] = EmptySquare;
7552         }
7553     } else {
7554         board[toY][toX] = board[fromY][fromX];
7555         board[fromY][fromX] = EmptySquare;
7556     }
7557
7558     /* [HGM] now we promote for Shogi, if needed */
7559     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7560         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7561   }
7562
7563     if (gameInfo.holdingsWidth != 0) {
7564
7565       /* !!A lot more code needs to be written to support holdings  */
7566       /* [HGM] OK, so I have written it. Holdings are stored in the */
7567       /* penultimate board files, so they are automaticlly stored   */
7568       /* in the game history.                                       */
7569       if (fromY == DROP_RANK) {
7570         /* Delete from holdings, by decreasing count */
7571         /* and erasing image if necessary            */
7572         p = (int) fromX;
7573         if(p < (int) BlackPawn) { /* white drop */
7574              p -= (int)WhitePawn;
7575                  p = PieceToNumber((ChessSquare)p);
7576              if(p >= gameInfo.holdingsSize) p = 0;
7577              if(--board[p][BOARD_WIDTH-2] <= 0)
7578                   board[p][BOARD_WIDTH-1] = EmptySquare;
7579              if((int)board[p][BOARD_WIDTH-2] < 0)
7580                         board[p][BOARD_WIDTH-2] = 0;
7581         } else {                  /* black drop */
7582              p -= (int)BlackPawn;
7583                  p = PieceToNumber((ChessSquare)p);
7584              if(p >= gameInfo.holdingsSize) p = 0;
7585              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7586                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7587              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7588                         board[BOARD_HEIGHT-1-p][1] = 0;
7589         }
7590       }
7591       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7592           && gameInfo.variant != VariantBughouse        ) {
7593         /* [HGM] holdings: Add to holdings, if holdings exist */
7594         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7595                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7596                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7597         }
7598         p = (int) captured;
7599         if (p >= (int) BlackPawn) {
7600           p -= (int)BlackPawn;
7601           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7602                   /* in Shogi restore piece to its original  first */
7603                   captured = (ChessSquare) (DEMOTED captured);
7604                   p = DEMOTED p;
7605           }
7606           p = PieceToNumber((ChessSquare)p);
7607           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7608           board[p][BOARD_WIDTH-2]++;
7609           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7610         } else {
7611           p -= (int)WhitePawn;
7612           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7613                   captured = (ChessSquare) (DEMOTED captured);
7614                   p = DEMOTED p;
7615           }
7616           p = PieceToNumber((ChessSquare)p);
7617           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7618           board[BOARD_HEIGHT-1-p][1]++;
7619           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7620         }
7621       }
7622     } else if (gameInfo.variant == VariantAtomic) {
7623       if (captured != EmptySquare) {
7624         int y, x;
7625         for (y = toY-1; y <= toY+1; y++) {
7626           for (x = toX-1; x <= toX+1; x++) {
7627             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7628                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7629               board[y][x] = EmptySquare;
7630             }
7631           }
7632         }
7633         board[toY][toX] = EmptySquare;
7634       }
7635     }
7636     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7637         /* [HGM] Shogi promotions */
7638         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7639     }
7640
7641     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7642                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7643         // [HGM] superchess: take promotion piece out of holdings
7644         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7645         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7646             if(!--board[k][BOARD_WIDTH-2])
7647                 board[k][BOARD_WIDTH-1] = EmptySquare;
7648         } else {
7649             if(!--board[BOARD_HEIGHT-1-k][1])
7650                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7651         }
7652     }
7653
7654 }
7655
7656 /* Updates forwardMostMove */
7657 void
7658 MakeMove(fromX, fromY, toX, toY, promoChar)
7659      int fromX, fromY, toX, toY;
7660      int promoChar;
7661 {
7662 //    forwardMostMove++; // [HGM] bare: moved downstream
7663
7664     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7665         int timeLeft; static int lastLoadFlag=0; int king, piece;
7666         piece = boards[forwardMostMove][fromY][fromX];
7667         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7668         if(gameInfo.variant == VariantKnightmate)
7669             king += (int) WhiteUnicorn - (int) WhiteKing;
7670         if(forwardMostMove == 0) {
7671             if(blackPlaysFirst) 
7672                 fprintf(serverMoves, "%s;", second.tidy);
7673             fprintf(serverMoves, "%s;", first.tidy);
7674             if(!blackPlaysFirst) 
7675                 fprintf(serverMoves, "%s;", second.tidy);
7676         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7677         lastLoadFlag = loadFlag;
7678         // print base move
7679         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7680         // print castling suffix
7681         if( toY == fromY && piece == king ) {
7682             if(toX-fromX > 1)
7683                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7684             if(fromX-toX >1)
7685                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7686         }
7687         // e.p. suffix
7688         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7689              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7690              boards[forwardMostMove][toY][toX] == EmptySquare
7691              && fromX != toX )
7692                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7693         // promotion suffix
7694         if(promoChar != NULLCHAR)
7695                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7696         if(!loadFlag) {
7697             fprintf(serverMoves, "/%d/%d",
7698                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7699             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7700             else                      timeLeft = blackTimeRemaining/1000;
7701             fprintf(serverMoves, "/%d", timeLeft);
7702         }
7703         fflush(serverMoves);
7704     }
7705
7706     if (forwardMostMove+1 >= MAX_MOVES) {
7707       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7708                         0, 1);
7709       return;
7710     }
7711     if (commentList[forwardMostMove+1] != NULL) {
7712         free(commentList[forwardMostMove+1]);
7713         commentList[forwardMostMove+1] = NULL;
7714     }
7715     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7716     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7717     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7718                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7719     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7720     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7721     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7722     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7723     gameInfo.result = GameUnfinished;
7724     if (gameInfo.resultDetails != NULL) {
7725         free(gameInfo.resultDetails);
7726         gameInfo.resultDetails = NULL;
7727     }
7728     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7729                               moveList[forwardMostMove - 1]);
7730     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7731                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7732                              fromY, fromX, toY, toX, promoChar,
7733                              parseList[forwardMostMove - 1]);
7734     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7735                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7736                             castlingRights[forwardMostMove]) ) {
7737       case MT_NONE:
7738       case MT_STALEMATE:
7739       default:
7740         break;
7741       case MT_CHECK:
7742         if(gameInfo.variant != VariantShogi)
7743             strcat(parseList[forwardMostMove - 1], "+");
7744         break;
7745       case MT_CHECKMATE:
7746       case MT_STAINMATE:
7747         strcat(parseList[forwardMostMove - 1], "#");
7748         break;
7749     }
7750     if (appData.debugMode) {
7751         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7752     }
7753
7754 }
7755
7756 /* Updates currentMove if not pausing */
7757 void
7758 ShowMove(fromX, fromY, toX, toY)
7759 {
7760     int instant = (gameMode == PlayFromGameFile) ?
7761         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7762     if(appData.noGUI) return;
7763     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7764         if (!instant) {
7765             if (forwardMostMove == currentMove + 1) {
7766                 AnimateMove(boards[forwardMostMove - 1],
7767                             fromX, fromY, toX, toY);
7768             }
7769             if (appData.highlightLastMove) {
7770                 SetHighlights(fromX, fromY, toX, toY);
7771             }
7772         }
7773         currentMove = forwardMostMove;
7774     }
7775
7776     if (instant) return;
7777
7778     DisplayMove(currentMove - 1);
7779     DrawPosition(FALSE, boards[currentMove]);
7780     DisplayBothClocks();
7781     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7782 }
7783
7784 void SendEgtPath(ChessProgramState *cps)
7785 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7786         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7787
7788         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7789
7790         while(*p) {
7791             char c, *q = name+1, *r, *s;
7792
7793             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7794             while(*p && *p != ',') *q++ = *p++;
7795             *q++ = ':'; *q = 0;
7796             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7797                 strcmp(name, ",nalimov:") == 0 ) {
7798                 // take nalimov path from the menu-changeable option first, if it is defined
7799                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7800                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7801             } else
7802             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7803                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7804                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7805                 s = r = StrStr(s, ":") + 1; // beginning of path info
7806                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7807                 c = *r; *r = 0;             // temporarily null-terminate path info
7808                     *--q = 0;               // strip of trailig ':' from name
7809                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7810                 *r = c;
7811                 SendToProgram(buf,cps);     // send egtbpath command for this format
7812             }
7813             if(*p == ',') p++; // read away comma to position for next format name
7814         }
7815 }
7816
7817 void
7818 InitChessProgram(cps, setup)
7819      ChessProgramState *cps;
7820      int setup; /* [HGM] needed to setup FRC opening position */
7821 {
7822     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7823     if (appData.noChessProgram) return;
7824     hintRequested = FALSE;
7825     bookRequested = FALSE;
7826
7827     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7828     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7829     if(cps->memSize) { /* [HGM] memory */
7830         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7831         SendToProgram(buf, cps);
7832     }
7833     SendEgtPath(cps); /* [HGM] EGT */
7834     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7835         sprintf(buf, "cores %d\n", appData.smpCores);
7836         SendToProgram(buf, cps);
7837     }
7838
7839     SendToProgram(cps->initString, cps);
7840     if (gameInfo.variant != VariantNormal &&
7841         gameInfo.variant != VariantLoadable
7842         /* [HGM] also send variant if board size non-standard */
7843         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7844                                             ) {
7845       char *v = VariantName(gameInfo.variant);
7846       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7847         /* [HGM] in protocol 1 we have to assume all variants valid */
7848         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7849         DisplayFatalError(buf, 0, 1);
7850         return;
7851       }
7852
7853       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7854       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7855       if( gameInfo.variant == VariantXiangqi )
7856            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7857       if( gameInfo.variant == VariantShogi )
7858            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7859       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7860            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7861       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7862                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7863            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7864       if( gameInfo.variant == VariantCourier )
7865            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7866       if( gameInfo.variant == VariantSuper )
7867            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7868       if( gameInfo.variant == VariantGreat )
7869            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7870
7871       if(overruled) {
7872            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7873                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7874            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7875            if(StrStr(cps->variants, b) == NULL) { 
7876                // specific sized variant not known, check if general sizing allowed
7877                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7878                    if(StrStr(cps->variants, "boardsize") == NULL) {
7879                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7880                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7881                        DisplayFatalError(buf, 0, 1);
7882                        return;
7883                    }
7884                    /* [HGM] here we really should compare with the maximum supported board size */
7885                }
7886            }
7887       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7888       sprintf(buf, "variant %s\n", b);
7889       SendToProgram(buf, cps);
7890     }
7891     currentlyInitializedVariant = gameInfo.variant;
7892
7893     /* [HGM] send opening position in FRC to first engine */
7894     if(setup) {
7895           SendToProgram("force\n", cps);
7896           SendBoard(cps, 0);
7897           /* engine is now in force mode! Set flag to wake it up after first move. */
7898           setboardSpoiledMachineBlack = 1;
7899     }
7900
7901     if (cps->sendICS) {
7902       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7903       SendToProgram(buf, cps);
7904     }
7905     cps->maybeThinking = FALSE;
7906     cps->offeredDraw = 0;
7907     if (!appData.icsActive) {
7908         SendTimeControl(cps, movesPerSession, timeControl,
7909                         timeIncrement, appData.searchDepth,
7910                         searchTime);
7911     }
7912     if (appData.showThinking 
7913         // [HGM] thinking: four options require thinking output to be sent
7914         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7915                                 ) {
7916         SendToProgram("post\n", cps);
7917     }
7918     SendToProgram("hard\n", cps);
7919     if (!appData.ponderNextMove) {
7920         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7921            it without being sure what state we are in first.  "hard"
7922            is not a toggle, so that one is OK.
7923          */
7924         SendToProgram("easy\n", cps);
7925     }
7926     if (cps->usePing) {
7927       sprintf(buf, "ping %d\n", ++cps->lastPing);
7928       SendToProgram(buf, cps);
7929     }
7930     cps->initDone = TRUE;
7931 }   
7932
7933
7934 void
7935 StartChessProgram(cps)
7936      ChessProgramState *cps;
7937 {
7938     char buf[MSG_SIZ];
7939     int err;
7940
7941     if (appData.noChessProgram) return;
7942     cps->initDone = FALSE;
7943
7944     if (strcmp(cps->host, "localhost") == 0) {
7945         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7946     } else if (*appData.remoteShell == NULLCHAR) {
7947         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7948     } else {
7949         if (*appData.remoteUser == NULLCHAR) {
7950           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7951                     cps->program);
7952         } else {
7953           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7954                     cps->host, appData.remoteUser, cps->program);
7955         }
7956         err = StartChildProcess(buf, "", &cps->pr);
7957     }
7958     
7959     if (err != 0) {
7960         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7961         DisplayFatalError(buf, err, 1);
7962         cps->pr = NoProc;
7963         cps->isr = NULL;
7964         return;
7965     }
7966     
7967     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7968     if (cps->protocolVersion > 1) {
7969       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7970       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7971       cps->comboCnt = 0;  //                and values of combo boxes
7972       SendToProgram(buf, cps);
7973     } else {
7974       SendToProgram("xboard\n", cps);
7975     }
7976 }
7977
7978
7979 void
7980 TwoMachinesEventIfReady P((void))
7981 {
7982   if (first.lastPing != first.lastPong) {
7983     DisplayMessage("", _("Waiting for first chess program"));
7984     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7985     return;
7986   }
7987   if (second.lastPing != second.lastPong) {
7988     DisplayMessage("", _("Waiting for second chess program"));
7989     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7990     return;
7991   }
7992   ThawUI();
7993   TwoMachinesEvent();
7994 }
7995
7996 void
7997 NextMatchGame P((void))
7998 {
7999     int index; /* [HGM] autoinc: step load index during match */
8000     Reset(FALSE, TRUE);
8001     if (*appData.loadGameFile != NULLCHAR) {
8002         index = appData.loadGameIndex;
8003         if(index < 0) { // [HGM] autoinc
8004             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8005             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8006         } 
8007         LoadGameFromFile(appData.loadGameFile,
8008                          index,
8009                          appData.loadGameFile, FALSE);
8010     } else if (*appData.loadPositionFile != NULLCHAR) {
8011         index = appData.loadPositionIndex;
8012         if(index < 0) { // [HGM] autoinc
8013             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8014             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8015         } 
8016         LoadPositionFromFile(appData.loadPositionFile,
8017                              index,
8018                              appData.loadPositionFile);
8019     }
8020     TwoMachinesEventIfReady();
8021 }
8022
8023 void UserAdjudicationEvent( int result )
8024 {
8025     ChessMove gameResult = GameIsDrawn;
8026
8027     if( result > 0 ) {
8028         gameResult = WhiteWins;
8029     }
8030     else if( result < 0 ) {
8031         gameResult = BlackWins;
8032     }
8033
8034     if( gameMode == TwoMachinesPlay ) {
8035         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8036     }
8037 }
8038
8039
8040 // [HGM] save: calculate checksum of game to make games easily identifiable
8041 int StringCheckSum(char *s)
8042 {
8043         int i = 0;
8044         if(s==NULL) return 0;
8045         while(*s) i = i*259 + *s++;
8046         return i;
8047 }
8048
8049 int GameCheckSum()
8050 {
8051         int i, sum=0;
8052         for(i=backwardMostMove; i<forwardMostMove; i++) {
8053                 sum += pvInfoList[i].depth;
8054                 sum += StringCheckSum(parseList[i]);
8055                 sum += StringCheckSum(commentList[i]);
8056                 sum *= 261;
8057         }
8058         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8059         return sum + StringCheckSum(commentList[i]);
8060 } // end of save patch
8061
8062 void
8063 GameEnds(result, resultDetails, whosays)
8064      ChessMove result;
8065      char *resultDetails;
8066      int whosays;
8067 {
8068     GameMode nextGameMode;
8069     int isIcsGame;
8070     char buf[MSG_SIZ];
8071
8072     if(endingGame) return; /* [HGM] crash: forbid recursion */
8073     endingGame = 1;
8074
8075     if (appData.debugMode) {
8076       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8077               result, resultDetails ? resultDetails : "(null)", whosays);
8078     }
8079
8080     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8081         /* If we are playing on ICS, the server decides when the
8082            game is over, but the engine can offer to draw, claim 
8083            a draw, or resign. 
8084          */
8085 #if ZIPPY
8086         if (appData.zippyPlay && first.initDone) {
8087             if (result == GameIsDrawn) {
8088                 /* In case draw still needs to be claimed */
8089                 SendToICS(ics_prefix);
8090                 SendToICS("draw\n");
8091             } else if (StrCaseStr(resultDetails, "resign")) {
8092                 SendToICS(ics_prefix);
8093                 SendToICS("resign\n");
8094             }
8095         }
8096 #endif
8097         endingGame = 0; /* [HGM] crash */
8098         return;
8099     }
8100
8101     /* If we're loading the game from a file, stop */
8102     if (whosays == GE_FILE) {
8103       (void) StopLoadGameTimer();
8104       gameFileFP = NULL;
8105     }
8106
8107     /* Cancel draw offers */
8108     first.offeredDraw = second.offeredDraw = 0;
8109
8110     /* If this is an ICS game, only ICS can really say it's done;
8111        if not, anyone can. */
8112     isIcsGame = (gameMode == IcsPlayingWhite || 
8113                  gameMode == IcsPlayingBlack || 
8114                  gameMode == IcsObserving    || 
8115                  gameMode == IcsExamining);
8116
8117     if (!isIcsGame || whosays == GE_ICS) {
8118         /* OK -- not an ICS game, or ICS said it was done */
8119         StopClocks();
8120         if (!isIcsGame && !appData.noChessProgram) 
8121           SetUserThinkingEnables();
8122     
8123         /* [HGM] if a machine claims the game end we verify this claim */
8124         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8125             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8126                 char claimer;
8127                 ChessMove trueResult = (ChessMove) -1;
8128
8129                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8130                                             first.twoMachinesColor[0] :
8131                                             second.twoMachinesColor[0] ;
8132
8133                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8134                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8135                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8136                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8137                 } else
8138                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8139                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8140                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8141                 } else
8142                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8143                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8144                 }
8145
8146                 // now verify win claims, but not in drop games, as we don't understand those yet
8147                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8148                                                  || gameInfo.variant == VariantGreat) &&
8149                     (result == WhiteWins && claimer == 'w' ||
8150                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8151                       if (appData.debugMode) {
8152                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8153                                 result, epStatus[forwardMostMove], forwardMostMove);
8154                       }
8155                       if(result != trueResult) {
8156                               sprintf(buf, "False win claim: '%s'", resultDetails);
8157                               result = claimer == 'w' ? BlackWins : WhiteWins;
8158                               resultDetails = buf;
8159                       }
8160                 } else
8161                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8162                     && (forwardMostMove <= backwardMostMove ||
8163                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8164                         (claimer=='b')==(forwardMostMove&1))
8165                                                                                   ) {
8166                       /* [HGM] verify: draws that were not flagged are false claims */
8167                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8168                       result = claimer == 'w' ? BlackWins : WhiteWins;
8169                       resultDetails = buf;
8170                 }
8171                 /* (Claiming a loss is accepted no questions asked!) */
8172             }
8173             /* [HGM] bare: don't allow bare King to win */
8174             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8175                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8176                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8177                && result != GameIsDrawn)
8178             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8179                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8180                         int p = (int)boards[forwardMostMove][i][j] - color;
8181                         if(p >= 0 && p <= (int)WhiteKing) k++;
8182                 }
8183                 if (appData.debugMode) {
8184                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8185                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8186                 }
8187                 if(k <= 1) {
8188                         result = GameIsDrawn;
8189                         sprintf(buf, "%s but bare king", resultDetails);
8190                         resultDetails = buf;
8191                 }
8192             }
8193         }
8194
8195
8196         if(serverMoves != NULL && !loadFlag) { char c = '=';
8197             if(result==WhiteWins) c = '+';
8198             if(result==BlackWins) c = '-';
8199             if(resultDetails != NULL)
8200                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8201         }
8202         if (resultDetails != NULL) {
8203             gameInfo.result = result;
8204             gameInfo.resultDetails = StrSave(resultDetails);
8205
8206             /* display last move only if game was not loaded from file */
8207             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8208                 DisplayMove(currentMove - 1);
8209     
8210             if (forwardMostMove != 0) {
8211                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8212                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8213                                                                 ) {
8214                     if (*appData.saveGameFile != NULLCHAR) {
8215                         SaveGameToFile(appData.saveGameFile, TRUE);
8216                     } else if (appData.autoSaveGames) {
8217                         AutoSaveGame();
8218                     }
8219                     if (*appData.savePositionFile != NULLCHAR) {
8220                         SavePositionToFile(appData.savePositionFile);
8221                     }
8222                 }
8223             }
8224
8225             /* Tell program how game ended in case it is learning */
8226             /* [HGM] Moved this to after saving the PGN, just in case */
8227             /* engine died and we got here through time loss. In that */
8228             /* case we will get a fatal error writing the pipe, which */
8229             /* would otherwise lose us the PGN.                       */
8230             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8231             /* output during GameEnds should never be fatal anymore   */
8232             if (gameMode == MachinePlaysWhite ||
8233                 gameMode == MachinePlaysBlack ||
8234                 gameMode == TwoMachinesPlay ||
8235                 gameMode == IcsPlayingWhite ||
8236                 gameMode == IcsPlayingBlack ||
8237                 gameMode == BeginningOfGame) {
8238                 char buf[MSG_SIZ];
8239                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8240                         resultDetails);
8241                 if (first.pr != NoProc) {
8242                     SendToProgram(buf, &first);
8243                 }
8244                 if (second.pr != NoProc &&
8245                     gameMode == TwoMachinesPlay) {
8246                     SendToProgram(buf, &second);
8247                 }
8248             }
8249         }
8250
8251         if (appData.icsActive) {
8252             if (appData.quietPlay &&
8253                 (gameMode == IcsPlayingWhite ||
8254                  gameMode == IcsPlayingBlack)) {
8255                 SendToICS(ics_prefix);
8256                 SendToICS("set shout 1\n");
8257             }
8258             nextGameMode = IcsIdle;
8259             ics_user_moved = FALSE;
8260             /* clean up premove.  It's ugly when the game has ended and the
8261              * premove highlights are still on the board.
8262              */
8263             if (gotPremove) {
8264               gotPremove = FALSE;
8265               ClearPremoveHighlights();
8266               DrawPosition(FALSE, boards[currentMove]);
8267             }
8268             if (whosays == GE_ICS) {
8269                 switch (result) {
8270                 case WhiteWins:
8271                     if (gameMode == IcsPlayingWhite)
8272                         PlayIcsWinSound();
8273                     else if(gameMode == IcsPlayingBlack)
8274                         PlayIcsLossSound();
8275                     break;
8276                 case BlackWins:
8277                     if (gameMode == IcsPlayingBlack)
8278                         PlayIcsWinSound();
8279                     else if(gameMode == IcsPlayingWhite)
8280                         PlayIcsLossSound();
8281                     break;
8282                 case GameIsDrawn:
8283                     PlayIcsDrawSound();
8284                     break;
8285                 default:
8286                     PlayIcsUnfinishedSound();
8287                 }
8288             }
8289         } else if (gameMode == EditGame ||
8290                    gameMode == PlayFromGameFile || 
8291                    gameMode == AnalyzeMode || 
8292                    gameMode == AnalyzeFile) {
8293             nextGameMode = gameMode;
8294         } else {
8295             nextGameMode = EndOfGame;
8296         }
8297         pausing = FALSE;
8298         ModeHighlight();
8299     } else {
8300         nextGameMode = gameMode;
8301     }
8302
8303     if (appData.noChessProgram) {
8304         gameMode = nextGameMode;
8305         ModeHighlight();
8306         endingGame = 0; /* [HGM] crash */
8307         return;
8308     }
8309
8310     if (first.reuse) {
8311         /* Put first chess program into idle state */
8312         if (first.pr != NoProc &&
8313             (gameMode == MachinePlaysWhite ||
8314              gameMode == MachinePlaysBlack ||
8315              gameMode == TwoMachinesPlay ||
8316              gameMode == IcsPlayingWhite ||
8317              gameMode == IcsPlayingBlack ||
8318              gameMode == BeginningOfGame)) {
8319             SendToProgram("force\n", &first);
8320             if (first.usePing) {
8321               char buf[MSG_SIZ];
8322               sprintf(buf, "ping %d\n", ++first.lastPing);
8323               SendToProgram(buf, &first);
8324             }
8325         }
8326     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8327         /* Kill off first chess program */
8328         if (first.isr != NULL)
8329           RemoveInputSource(first.isr);
8330         first.isr = NULL;
8331     
8332         if (first.pr != NoProc) {
8333             ExitAnalyzeMode();
8334             DoSleep( appData.delayBeforeQuit );
8335             SendToProgram("quit\n", &first);
8336             DoSleep( appData.delayAfterQuit );
8337             DestroyChildProcess(first.pr, first.useSigterm);
8338         }
8339         first.pr = NoProc;
8340     }
8341     if (second.reuse) {
8342         /* Put second chess program into idle state */
8343         if (second.pr != NoProc &&
8344             gameMode == TwoMachinesPlay) {
8345             SendToProgram("force\n", &second);
8346             if (second.usePing) {
8347               char buf[MSG_SIZ];
8348               sprintf(buf, "ping %d\n", ++second.lastPing);
8349               SendToProgram(buf, &second);
8350             }
8351         }
8352     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8353         /* Kill off second chess program */
8354         if (second.isr != NULL)
8355           RemoveInputSource(second.isr);
8356         second.isr = NULL;
8357     
8358         if (second.pr != NoProc) {
8359             DoSleep( appData.delayBeforeQuit );
8360             SendToProgram("quit\n", &second);
8361             DoSleep( appData.delayAfterQuit );
8362             DestroyChildProcess(second.pr, second.useSigterm);
8363         }
8364         second.pr = NoProc;
8365     }
8366
8367     if (matchMode && gameMode == TwoMachinesPlay) {
8368         switch (result) {
8369         case WhiteWins:
8370           if (first.twoMachinesColor[0] == 'w') {
8371             first.matchWins++;
8372           } else {
8373             second.matchWins++;
8374           }
8375           break;
8376         case BlackWins:
8377           if (first.twoMachinesColor[0] == 'b') {
8378             first.matchWins++;
8379           } else {
8380             second.matchWins++;
8381           }
8382           break;
8383         default:
8384           break;
8385         }
8386         if (matchGame < appData.matchGames) {
8387             char *tmp;
8388             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8389                 tmp = first.twoMachinesColor;
8390                 first.twoMachinesColor = second.twoMachinesColor;
8391                 second.twoMachinesColor = tmp;
8392             }
8393             gameMode = nextGameMode;
8394             matchGame++;
8395             if(appData.matchPause>10000 || appData.matchPause<10)
8396                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8397             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8398             endingGame = 0; /* [HGM] crash */
8399             return;
8400         } else {
8401             char buf[MSG_SIZ];
8402             gameMode = nextGameMode;
8403             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8404                     first.tidy, second.tidy,
8405                     first.matchWins, second.matchWins,
8406                     appData.matchGames - (first.matchWins + second.matchWins));
8407             DisplayFatalError(buf, 0, 0);
8408         }
8409     }
8410     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8411         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8412       ExitAnalyzeMode();
8413     gameMode = nextGameMode;
8414     ModeHighlight();
8415     endingGame = 0;  /* [HGM] crash */
8416 }
8417
8418 /* Assumes program was just initialized (initString sent).
8419    Leaves program in force mode. */
8420 void
8421 FeedMovesToProgram(cps, upto) 
8422      ChessProgramState *cps;
8423      int upto;
8424 {
8425     int i;
8426     
8427     if (appData.debugMode)
8428       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8429               startedFromSetupPosition ? "position and " : "",
8430               backwardMostMove, upto, cps->which);
8431     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8432         // [HGM] variantswitch: make engine aware of new variant
8433         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8434                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8435         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8436         SendToProgram(buf, cps);
8437         currentlyInitializedVariant = gameInfo.variant;
8438     }
8439     SendToProgram("force\n", cps);
8440     if (startedFromSetupPosition) {
8441         SendBoard(cps, backwardMostMove);
8442     if (appData.debugMode) {
8443         fprintf(debugFP, "feedMoves\n");
8444     }
8445     }
8446     for (i = backwardMostMove; i < upto; i++) {
8447         SendMoveToProgram(i, cps);
8448     }
8449 }
8450
8451
8452 void
8453 ResurrectChessProgram()
8454 {
8455      /* The chess program may have exited.
8456         If so, restart it and feed it all the moves made so far. */
8457
8458     if (appData.noChessProgram || first.pr != NoProc) return;
8459     
8460     StartChessProgram(&first);
8461     InitChessProgram(&first, FALSE);
8462     FeedMovesToProgram(&first, currentMove);
8463
8464     if (!first.sendTime) {
8465         /* can't tell gnuchess what its clock should read,
8466            so we bow to its notion. */
8467         ResetClocks();
8468         timeRemaining[0][currentMove] = whiteTimeRemaining;
8469         timeRemaining[1][currentMove] = blackTimeRemaining;
8470     }
8471
8472     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8473                 appData.icsEngineAnalyze) && first.analysisSupport) {
8474       SendToProgram("analyze\n", &first);
8475       first.analyzing = TRUE;
8476     }
8477 }
8478
8479 /*
8480  * Button procedures
8481  */
8482 void
8483 Reset(redraw, init)
8484      int redraw, init;
8485 {
8486     int i;
8487
8488     if (appData.debugMode) {
8489         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8490                 redraw, init, gameMode);
8491     }
8492     pausing = pauseExamInvalid = FALSE;
8493     startedFromSetupPosition = blackPlaysFirst = FALSE;
8494     firstMove = TRUE;
8495     whiteFlag = blackFlag = FALSE;
8496     userOfferedDraw = FALSE;
8497     hintRequested = bookRequested = FALSE;
8498     first.maybeThinking = FALSE;
8499     second.maybeThinking = FALSE;
8500     first.bookSuspend = FALSE; // [HGM] book
8501     second.bookSuspend = FALSE;
8502     thinkOutput[0] = NULLCHAR;
8503     lastHint[0] = NULLCHAR;
8504     ClearGameInfo(&gameInfo);
8505     gameInfo.variant = StringToVariant(appData.variant);
8506     ics_user_moved = ics_clock_paused = FALSE;
8507     ics_getting_history = H_FALSE;
8508     ics_gamenum = -1;
8509     white_holding[0] = black_holding[0] = NULLCHAR;
8510     ClearProgramStats();
8511     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8512     
8513     ResetFrontEnd();
8514     ClearHighlights();
8515     flipView = appData.flipView;
8516     ClearPremoveHighlights();
8517     gotPremove = FALSE;
8518     alarmSounded = FALSE;
8519
8520     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8521     if(appData.serverMovesName != NULL) {
8522         /* [HGM] prepare to make moves file for broadcasting */
8523         clock_t t = clock();
8524         if(serverMoves != NULL) fclose(serverMoves);
8525         serverMoves = fopen(appData.serverMovesName, "r");
8526         if(serverMoves != NULL) {
8527             fclose(serverMoves);
8528             /* delay 15 sec before overwriting, so all clients can see end */
8529             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8530         }
8531         serverMoves = fopen(appData.serverMovesName, "w");
8532     }
8533
8534     ExitAnalyzeMode();
8535     gameMode = BeginningOfGame;
8536     ModeHighlight();
8537     if(appData.icsActive) gameInfo.variant = VariantNormal;
8538     currentMove = forwardMostMove = backwardMostMove = 0;
8539     InitPosition(redraw);
8540     for (i = 0; i < MAX_MOVES; i++) {
8541         if (commentList[i] != NULL) {
8542             free(commentList[i]);
8543             commentList[i] = NULL;
8544         }
8545     }
8546     ResetClocks();
8547     timeRemaining[0][0] = whiteTimeRemaining;
8548     timeRemaining[1][0] = blackTimeRemaining;
8549     if (first.pr == NULL) {
8550         StartChessProgram(&first);
8551     }
8552     if (init) {
8553             InitChessProgram(&first, startedFromSetupPosition);
8554     }
8555     DisplayTitle("");
8556     DisplayMessage("", "");
8557     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8558     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8559 }
8560
8561 void
8562 AutoPlayGameLoop()
8563 {
8564     for (;;) {
8565         if (!AutoPlayOneMove())
8566           return;
8567         if (matchMode || appData.timeDelay == 0)
8568           continue;
8569         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8570           return;
8571         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8572         break;
8573     }
8574 }
8575
8576
8577 int
8578 AutoPlayOneMove()
8579 {
8580     int fromX, fromY, toX, toY;
8581
8582     if (appData.debugMode) {
8583       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8584     }
8585
8586     if (gameMode != PlayFromGameFile)
8587       return FALSE;
8588
8589     if (currentMove >= forwardMostMove) {
8590       gameMode = EditGame;
8591       ModeHighlight();
8592
8593       /* [AS] Clear current move marker at the end of a game */
8594       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8595
8596       return FALSE;
8597     }
8598     
8599     toX = moveList[currentMove][2] - AAA;
8600     toY = moveList[currentMove][3] - ONE;
8601
8602     if (moveList[currentMove][1] == '@') {
8603         if (appData.highlightLastMove) {
8604             SetHighlights(-1, -1, toX, toY);
8605         }
8606     } else {
8607         fromX = moveList[currentMove][0] - AAA;
8608         fromY = moveList[currentMove][1] - ONE;
8609
8610         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8611
8612         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8613
8614         if (appData.highlightLastMove) {
8615             SetHighlights(fromX, fromY, toX, toY);
8616         }
8617     }
8618     DisplayMove(currentMove);
8619     SendMoveToProgram(currentMove++, &first);
8620     DisplayBothClocks();
8621     DrawPosition(FALSE, boards[currentMove]);
8622     // [HGM] PV info: always display, routine tests if empty
8623     DisplayComment(currentMove - 1, commentList[currentMove]);
8624     return TRUE;
8625 }
8626
8627
8628 int
8629 LoadGameOneMove(readAhead)
8630      ChessMove readAhead;
8631 {
8632     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8633     char promoChar = NULLCHAR;
8634     ChessMove moveType;
8635     char move[MSG_SIZ];
8636     char *p, *q;
8637     
8638     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8639         gameMode != AnalyzeMode && gameMode != Training) {
8640         gameFileFP = NULL;
8641         return FALSE;
8642     }
8643     
8644     yyboardindex = forwardMostMove;
8645     if (readAhead != (ChessMove)0) {
8646       moveType = readAhead;
8647     } else {
8648       if (gameFileFP == NULL)
8649           return FALSE;
8650       moveType = (ChessMove) yylex();
8651     }
8652     
8653     done = FALSE;
8654     switch (moveType) {
8655       case Comment:
8656         if (appData.debugMode) 
8657           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8658         p = yy_text;
8659         if (*p == '{' || *p == '[' || *p == '(') {
8660             p[strlen(p) - 1] = NULLCHAR;
8661             p++;
8662         }
8663
8664         /* append the comment but don't display it */
8665         while (*p == '\n') p++;
8666         AppendComment(currentMove, p);
8667         return TRUE;
8668
8669       case WhiteCapturesEnPassant:
8670       case BlackCapturesEnPassant:
8671       case WhitePromotionChancellor:
8672       case BlackPromotionChancellor:
8673       case WhitePromotionArchbishop:
8674       case BlackPromotionArchbishop:
8675       case WhitePromotionCentaur:
8676       case BlackPromotionCentaur:
8677       case WhitePromotionQueen:
8678       case BlackPromotionQueen:
8679       case WhitePromotionRook:
8680       case BlackPromotionRook:
8681       case WhitePromotionBishop:
8682       case BlackPromotionBishop:
8683       case WhitePromotionKnight:
8684       case BlackPromotionKnight:
8685       case WhitePromotionKing:
8686       case BlackPromotionKing:
8687       case NormalMove:
8688       case WhiteKingSideCastle:
8689       case WhiteQueenSideCastle:
8690       case BlackKingSideCastle:
8691       case BlackQueenSideCastle:
8692       case WhiteKingSideCastleWild:
8693       case WhiteQueenSideCastleWild:
8694       case BlackKingSideCastleWild:
8695       case BlackQueenSideCastleWild:
8696       /* PUSH Fabien */
8697       case WhiteHSideCastleFR:
8698       case WhiteASideCastleFR:
8699       case BlackHSideCastleFR:
8700       case BlackASideCastleFR:
8701       /* POP Fabien */
8702         if (appData.debugMode)
8703           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8704         fromX = currentMoveString[0] - AAA;
8705         fromY = currentMoveString[1] - ONE;
8706         toX = currentMoveString[2] - AAA;
8707         toY = currentMoveString[3] - ONE;
8708         promoChar = currentMoveString[4];
8709         break;
8710
8711       case WhiteDrop:
8712       case BlackDrop:
8713         if (appData.debugMode)
8714           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8715         fromX = moveType == WhiteDrop ?
8716           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8717         (int) CharToPiece(ToLower(currentMoveString[0]));
8718         fromY = DROP_RANK;
8719         toX = currentMoveString[2] - AAA;
8720         toY = currentMoveString[3] - ONE;
8721         break;
8722
8723       case WhiteWins:
8724       case BlackWins:
8725       case GameIsDrawn:
8726       case GameUnfinished:
8727         if (appData.debugMode)
8728           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8729         p = strchr(yy_text, '{');
8730         if (p == NULL) p = strchr(yy_text, '(');
8731         if (p == NULL) {
8732             p = yy_text;
8733             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8734         } else {
8735             q = strchr(p, *p == '{' ? '}' : ')');
8736             if (q != NULL) *q = NULLCHAR;
8737             p++;
8738         }
8739         GameEnds(moveType, p, GE_FILE);
8740         done = TRUE;
8741         if (cmailMsgLoaded) {
8742             ClearHighlights();
8743             flipView = WhiteOnMove(currentMove);
8744             if (moveType == GameUnfinished) flipView = !flipView;
8745             if (appData.debugMode)
8746               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8747         }
8748         break;
8749
8750       case (ChessMove) 0:       /* end of file */
8751         if (appData.debugMode)
8752           fprintf(debugFP, "Parser hit end of file\n");
8753         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8754                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8755           case MT_NONE:
8756           case MT_CHECK:
8757             break;
8758           case MT_CHECKMATE:
8759           case MT_STAINMATE:
8760             if (WhiteOnMove(currentMove)) {
8761                 GameEnds(BlackWins, "Black mates", GE_FILE);
8762             } else {
8763                 GameEnds(WhiteWins, "White mates", GE_FILE);
8764             }
8765             break;
8766           case MT_STALEMATE:
8767             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8768             break;
8769         }
8770         done = TRUE;
8771         break;
8772
8773       case MoveNumberOne:
8774         if (lastLoadGameStart == GNUChessGame) {
8775             /* GNUChessGames have numbers, but they aren't move numbers */
8776             if (appData.debugMode)
8777               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8778                       yy_text, (int) moveType);
8779             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8780         }
8781         /* else fall thru */
8782
8783       case XBoardGame:
8784       case GNUChessGame:
8785       case PGNTag:
8786         /* Reached start of next game in file */
8787         if (appData.debugMode)
8788           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8789         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8790                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8791           case MT_NONE:
8792           case MT_CHECK:
8793             break;
8794           case MT_CHECKMATE:
8795           case MT_STAINMATE:
8796             if (WhiteOnMove(currentMove)) {
8797                 GameEnds(BlackWins, "Black mates", GE_FILE);
8798             } else {
8799                 GameEnds(WhiteWins, "White mates", GE_FILE);
8800             }
8801             break;
8802           case MT_STALEMATE:
8803             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8804             break;
8805         }
8806         done = TRUE;
8807         break;
8808
8809       case PositionDiagram:     /* should not happen; ignore */
8810       case ElapsedTime:         /* ignore */
8811       case NAG:                 /* ignore */
8812         if (appData.debugMode)
8813           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8814                   yy_text, (int) moveType);
8815         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8816
8817       case IllegalMove:
8818         if (appData.testLegality) {
8819             if (appData.debugMode)
8820               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8821             sprintf(move, _("Illegal move: %d.%s%s"),
8822                     (forwardMostMove / 2) + 1,
8823                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8824             DisplayError(move, 0);
8825             done = TRUE;
8826         } else {
8827             if (appData.debugMode)
8828               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8829                       yy_text, currentMoveString);
8830             fromX = currentMoveString[0] - AAA;
8831             fromY = currentMoveString[1] - ONE;
8832             toX = currentMoveString[2] - AAA;
8833             toY = currentMoveString[3] - ONE;
8834             promoChar = currentMoveString[4];
8835         }
8836         break;
8837
8838       case AmbiguousMove:
8839         if (appData.debugMode)
8840           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8841         sprintf(move, _("Ambiguous move: %d.%s%s"),
8842                 (forwardMostMove / 2) + 1,
8843                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8844         DisplayError(move, 0);
8845         done = TRUE;
8846         break;
8847
8848       default:
8849       case ImpossibleMove:
8850         if (appData.debugMode)
8851           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8852         sprintf(move, _("Illegal move: %d.%s%s"),
8853                 (forwardMostMove / 2) + 1,
8854                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8855         DisplayError(move, 0);
8856         done = TRUE;
8857         break;
8858     }
8859
8860     if (done) {
8861         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8862             DrawPosition(FALSE, boards[currentMove]);
8863             DisplayBothClocks();
8864             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8865               DisplayComment(currentMove - 1, commentList[currentMove]);
8866         }
8867         (void) StopLoadGameTimer();
8868         gameFileFP = NULL;
8869         cmailOldMove = forwardMostMove;
8870         return FALSE;
8871     } else {
8872         /* currentMoveString is set as a side-effect of yylex */
8873         strcat(currentMoveString, "\n");
8874         strcpy(moveList[forwardMostMove], currentMoveString);
8875         
8876         thinkOutput[0] = NULLCHAR;
8877         MakeMove(fromX, fromY, toX, toY, promoChar);
8878         currentMove = forwardMostMove;
8879         return TRUE;
8880     }
8881 }
8882
8883 /* Load the nth game from the given file */
8884 int
8885 LoadGameFromFile(filename, n, title, useList)
8886      char *filename;
8887      int n;
8888      char *title;
8889      /*Boolean*/ int useList;
8890 {
8891     FILE *f;
8892     char buf[MSG_SIZ];
8893
8894     if (strcmp(filename, "-") == 0) {
8895         f = stdin;
8896         title = "stdin";
8897     } else {
8898         f = fopen(filename, "rb");
8899         if (f == NULL) {
8900           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8901             DisplayError(buf, errno);
8902             return FALSE;
8903         }
8904     }
8905     if (fseek(f, 0, 0) == -1) {
8906         /* f is not seekable; probably a pipe */
8907         useList = FALSE;
8908     }
8909     if (useList && n == 0) {
8910         int error = GameListBuild(f);
8911         if (error) {
8912             DisplayError(_("Cannot build game list"), error);
8913         } else if (!ListEmpty(&gameList) &&
8914                    ((ListGame *) gameList.tailPred)->number > 1) {
8915             GameListPopUp(f, title);
8916             return TRUE;
8917         }
8918         GameListDestroy();
8919         n = 1;
8920     }
8921     if (n == 0) n = 1;
8922     return LoadGame(f, n, title, FALSE);
8923 }
8924
8925
8926 void
8927 MakeRegisteredMove()
8928 {
8929     int fromX, fromY, toX, toY;
8930     char promoChar;
8931     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8932         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8933           case CMAIL_MOVE:
8934           case CMAIL_DRAW:
8935             if (appData.debugMode)
8936               fprintf(debugFP, "Restoring %s for game %d\n",
8937                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8938     
8939             thinkOutput[0] = NULLCHAR;
8940             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8941             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8942             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8943             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8944             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8945             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8946             MakeMove(fromX, fromY, toX, toY, promoChar);
8947             ShowMove(fromX, fromY, toX, toY);
8948               
8949             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8950                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8951               case MT_NONE:
8952               case MT_CHECK:
8953                 break;
8954                 
8955               case MT_CHECKMATE:
8956               case MT_STAINMATE:
8957                 if (WhiteOnMove(currentMove)) {
8958                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8959                 } else {
8960                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8961                 }
8962                 break;
8963                 
8964               case MT_STALEMATE:
8965                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8966                 break;
8967             }
8968
8969             break;
8970             
8971           case CMAIL_RESIGN:
8972             if (WhiteOnMove(currentMove)) {
8973                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8974             } else {
8975                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8976             }
8977             break;
8978             
8979           case CMAIL_ACCEPT:
8980             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8981             break;
8982               
8983           default:
8984             break;
8985         }
8986     }
8987
8988     return;
8989 }
8990
8991 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8992 int
8993 CmailLoadGame(f, gameNumber, title, useList)
8994      FILE *f;
8995      int gameNumber;
8996      char *title;
8997      int useList;
8998 {
8999     int retVal;
9000
9001     if (gameNumber > nCmailGames) {
9002         DisplayError(_("No more games in this message"), 0);
9003         return FALSE;
9004     }
9005     if (f == lastLoadGameFP) {
9006         int offset = gameNumber - lastLoadGameNumber;
9007         if (offset == 0) {
9008             cmailMsg[0] = NULLCHAR;
9009             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9010                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9011                 nCmailMovesRegistered--;
9012             }
9013             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9014             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9015                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9016             }
9017         } else {
9018             if (! RegisterMove()) return FALSE;
9019         }
9020     }
9021
9022     retVal = LoadGame(f, gameNumber, title, useList);
9023
9024     /* Make move registered during previous look at this game, if any */
9025     MakeRegisteredMove();
9026
9027     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9028         commentList[currentMove]
9029           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9030         DisplayComment(currentMove - 1, commentList[currentMove]);
9031     }
9032
9033     return retVal;
9034 }
9035
9036 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9037 int
9038 ReloadGame(offset)
9039      int offset;
9040 {
9041     int gameNumber = lastLoadGameNumber + offset;
9042     if (lastLoadGameFP == NULL) {
9043         DisplayError(_("No game has been loaded yet"), 0);
9044         return FALSE;
9045     }
9046     if (gameNumber <= 0) {
9047         DisplayError(_("Can't back up any further"), 0);
9048         return FALSE;
9049     }
9050     if (cmailMsgLoaded) {
9051         return CmailLoadGame(lastLoadGameFP, gameNumber,
9052                              lastLoadGameTitle, lastLoadGameUseList);
9053     } else {
9054         return LoadGame(lastLoadGameFP, gameNumber,
9055                         lastLoadGameTitle, lastLoadGameUseList);
9056     }
9057 }
9058
9059
9060
9061 /* Load the nth game from open file f */
9062 int
9063 LoadGame(f, gameNumber, title, useList)
9064      FILE *f;
9065      int gameNumber;
9066      char *title;
9067      int useList;
9068 {
9069     ChessMove cm;
9070     char buf[MSG_SIZ];
9071     int gn = gameNumber;
9072     ListGame *lg = NULL;
9073     int numPGNTags = 0;
9074     int err;
9075     GameMode oldGameMode;
9076     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9077
9078     if (appData.debugMode) 
9079         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9080
9081     if (gameMode == Training )
9082         SetTrainingModeOff();
9083
9084     oldGameMode = gameMode;
9085     if (gameMode != BeginningOfGame) {
9086       Reset(FALSE, TRUE);
9087     }
9088
9089     gameFileFP = f;
9090     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9091         fclose(lastLoadGameFP);
9092     }
9093
9094     if (useList) {
9095         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9096         
9097         if (lg) {
9098             fseek(f, lg->offset, 0);
9099             GameListHighlight(gameNumber);
9100             gn = 1;
9101         }
9102         else {
9103             DisplayError(_("Game number out of range"), 0);
9104             return FALSE;
9105         }
9106     } else {
9107         GameListDestroy();
9108         if (fseek(f, 0, 0) == -1) {
9109             if (f == lastLoadGameFP ?
9110                 gameNumber == lastLoadGameNumber + 1 :
9111                 gameNumber == 1) {
9112                 gn = 1;
9113             } else {
9114                 DisplayError(_("Can't seek on game file"), 0);
9115                 return FALSE;
9116             }
9117         }
9118     }
9119     lastLoadGameFP = f;
9120     lastLoadGameNumber = gameNumber;
9121     strcpy(lastLoadGameTitle, title);
9122     lastLoadGameUseList = useList;
9123
9124     yynewfile(f);
9125
9126     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9127       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9128                 lg->gameInfo.black);
9129             DisplayTitle(buf);
9130     } else if (*title != NULLCHAR) {
9131         if (gameNumber > 1) {
9132             sprintf(buf, "%s %d", title, gameNumber);
9133             DisplayTitle(buf);
9134         } else {
9135             DisplayTitle(title);
9136         }
9137     }
9138
9139     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9140         gameMode = PlayFromGameFile;
9141         ModeHighlight();
9142     }
9143
9144     currentMove = forwardMostMove = backwardMostMove = 0;
9145     CopyBoard(boards[0], initialPosition);
9146     StopClocks();
9147
9148     /*
9149      * Skip the first gn-1 games in the file.
9150      * Also skip over anything that precedes an identifiable 
9151      * start of game marker, to avoid being confused by 
9152      * garbage at the start of the file.  Currently 
9153      * recognized start of game markers are the move number "1",
9154      * the pattern "gnuchess .* game", the pattern
9155      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9156      * A game that starts with one of the latter two patterns
9157      * will also have a move number 1, possibly
9158      * following a position diagram.
9159      * 5-4-02: Let's try being more lenient and allowing a game to
9160      * start with an unnumbered move.  Does that break anything?
9161      */
9162     cm = lastLoadGameStart = (ChessMove) 0;
9163     while (gn > 0) {
9164         yyboardindex = forwardMostMove;
9165         cm = (ChessMove) yylex();
9166         switch (cm) {
9167           case (ChessMove) 0:
9168             if (cmailMsgLoaded) {
9169                 nCmailGames = CMAIL_MAX_GAMES - gn;
9170             } else {
9171                 Reset(TRUE, TRUE);
9172                 DisplayError(_("Game not found in file"), 0);
9173             }
9174             return FALSE;
9175
9176           case GNUChessGame:
9177           case XBoardGame:
9178             gn--;
9179             lastLoadGameStart = cm;
9180             break;
9181             
9182           case MoveNumberOne:
9183             switch (lastLoadGameStart) {
9184               case GNUChessGame:
9185               case XBoardGame:
9186               case PGNTag:
9187                 break;
9188               case MoveNumberOne:
9189               case (ChessMove) 0:
9190                 gn--;           /* count this game */
9191                 lastLoadGameStart = cm;
9192                 break;
9193               default:
9194                 /* impossible */
9195                 break;
9196             }
9197             break;
9198
9199           case PGNTag:
9200             switch (lastLoadGameStart) {
9201               case GNUChessGame:
9202               case PGNTag:
9203               case MoveNumberOne:
9204               case (ChessMove) 0:
9205                 gn--;           /* count this game */
9206                 lastLoadGameStart = cm;
9207                 break;
9208               case XBoardGame:
9209                 lastLoadGameStart = cm; /* game counted already */
9210                 break;
9211               default:
9212                 /* impossible */
9213                 break;
9214             }
9215             if (gn > 0) {
9216                 do {
9217                     yyboardindex = forwardMostMove;
9218                     cm = (ChessMove) yylex();
9219                 } while (cm == PGNTag || cm == Comment);
9220             }
9221             break;
9222
9223           case WhiteWins:
9224           case BlackWins:
9225           case GameIsDrawn:
9226             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9227                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9228                     != CMAIL_OLD_RESULT) {
9229                     nCmailResults ++ ;
9230                     cmailResult[  CMAIL_MAX_GAMES
9231                                 - gn - 1] = CMAIL_OLD_RESULT;
9232                 }
9233             }
9234             break;
9235
9236           case NormalMove:
9237             /* Only a NormalMove can be at the start of a game
9238              * without a position diagram. */
9239             if (lastLoadGameStart == (ChessMove) 0) {
9240               gn--;
9241               lastLoadGameStart = MoveNumberOne;
9242             }
9243             break;
9244
9245           default:
9246             break;
9247         }
9248     }
9249     
9250     if (appData.debugMode)
9251       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9252
9253     if (cm == XBoardGame) {
9254         /* Skip any header junk before position diagram and/or move 1 */
9255         for (;;) {
9256             yyboardindex = forwardMostMove;
9257             cm = (ChessMove) yylex();
9258
9259             if (cm == (ChessMove) 0 ||
9260                 cm == GNUChessGame || cm == XBoardGame) {
9261                 /* Empty game; pretend end-of-file and handle later */
9262                 cm = (ChessMove) 0;
9263                 break;
9264             }
9265
9266             if (cm == MoveNumberOne || cm == PositionDiagram ||
9267                 cm == PGNTag || cm == Comment)
9268               break;
9269         }
9270     } else if (cm == GNUChessGame) {
9271         if (gameInfo.event != NULL) {
9272             free(gameInfo.event);
9273         }
9274         gameInfo.event = StrSave(yy_text);
9275     }   
9276
9277     startedFromSetupPosition = FALSE;
9278     while (cm == PGNTag) {
9279         if (appData.debugMode) 
9280           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9281         err = ParsePGNTag(yy_text, &gameInfo);
9282         if (!err) numPGNTags++;
9283
9284         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9285         if(gameInfo.variant != oldVariant) {
9286             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9287             InitPosition(TRUE);
9288             oldVariant = gameInfo.variant;
9289             if (appData.debugMode) 
9290               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9291         }
9292
9293
9294         if (gameInfo.fen != NULL) {
9295           Board initial_position;
9296           startedFromSetupPosition = TRUE;
9297           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9298             Reset(TRUE, TRUE);
9299             DisplayError(_("Bad FEN position in file"), 0);
9300             return FALSE;
9301           }
9302           CopyBoard(boards[0], initial_position);
9303           if (blackPlaysFirst) {
9304             currentMove = forwardMostMove = backwardMostMove = 1;
9305             CopyBoard(boards[1], initial_position);
9306             strcpy(moveList[0], "");
9307             strcpy(parseList[0], "");
9308             timeRemaining[0][1] = whiteTimeRemaining;
9309             timeRemaining[1][1] = blackTimeRemaining;
9310             if (commentList[0] != NULL) {
9311               commentList[1] = commentList[0];
9312               commentList[0] = NULL;
9313             }
9314           } else {
9315             currentMove = forwardMostMove = backwardMostMove = 0;
9316           }
9317           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9318           {   int i;
9319               initialRulePlies = FENrulePlies;
9320               epStatus[forwardMostMove] = FENepStatus;
9321               for( i=0; i< nrCastlingRights; i++ )
9322                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9323           }
9324           yyboardindex = forwardMostMove;
9325           free(gameInfo.fen);
9326           gameInfo.fen = NULL;
9327         }
9328
9329         yyboardindex = forwardMostMove;
9330         cm = (ChessMove) yylex();
9331
9332         /* Handle comments interspersed among the tags */
9333         while (cm == Comment) {
9334             char *p;
9335             if (appData.debugMode) 
9336               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9337             p = yy_text;
9338             if (*p == '{' || *p == '[' || *p == '(') {
9339                 p[strlen(p) - 1] = NULLCHAR;
9340                 p++;
9341             }
9342             while (*p == '\n') p++;
9343             AppendComment(currentMove, p);
9344             yyboardindex = forwardMostMove;
9345             cm = (ChessMove) yylex();
9346         }
9347     }
9348
9349     /* don't rely on existence of Event tag since if game was
9350      * pasted from clipboard the Event tag may not exist
9351      */
9352     if (numPGNTags > 0){
9353         char *tags;
9354         if (gameInfo.variant == VariantNormal) {
9355           gameInfo.variant = StringToVariant(gameInfo.event);
9356         }
9357         if (!matchMode) {
9358           if( appData.autoDisplayTags ) {
9359             tags = PGNTags(&gameInfo);
9360             TagsPopUp(tags, CmailMsg());
9361             free(tags);
9362           }
9363         }
9364     } else {
9365         /* Make something up, but don't display it now */
9366         SetGameInfo();
9367         TagsPopDown();
9368     }
9369
9370     if (cm == PositionDiagram) {
9371         int i, j;
9372         char *p;
9373         Board initial_position;
9374
9375         if (appData.debugMode)
9376           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9377
9378         if (!startedFromSetupPosition) {
9379             p = yy_text;
9380             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9381               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9382                 switch (*p) {
9383                   case '[':
9384                   case '-':
9385                   case ' ':
9386                   case '\t':
9387                   case '\n':
9388                   case '\r':
9389                     break;
9390                   default:
9391                     initial_position[i][j++] = CharToPiece(*p);
9392                     break;
9393                 }
9394             while (*p == ' ' || *p == '\t' ||
9395                    *p == '\n' || *p == '\r') p++;
9396         
9397             if (strncmp(p, "black", strlen("black"))==0)
9398               blackPlaysFirst = TRUE;
9399             else
9400               blackPlaysFirst = FALSE;
9401             startedFromSetupPosition = TRUE;
9402         
9403             CopyBoard(boards[0], initial_position);
9404             if (blackPlaysFirst) {
9405                 currentMove = forwardMostMove = backwardMostMove = 1;
9406                 CopyBoard(boards[1], initial_position);
9407                 strcpy(moveList[0], "");
9408                 strcpy(parseList[0], "");
9409                 timeRemaining[0][1] = whiteTimeRemaining;
9410                 timeRemaining[1][1] = blackTimeRemaining;
9411                 if (commentList[0] != NULL) {
9412                     commentList[1] = commentList[0];
9413                     commentList[0] = NULL;
9414                 }
9415             } else {
9416                 currentMove = forwardMostMove = backwardMostMove = 0;
9417             }
9418         }
9419         yyboardindex = forwardMostMove;
9420         cm = (ChessMove) yylex();
9421     }
9422
9423     if (first.pr == NoProc) {
9424         StartChessProgram(&first);
9425     }
9426     InitChessProgram(&first, FALSE);
9427     SendToProgram("force\n", &first);
9428     if (startedFromSetupPosition) {
9429         SendBoard(&first, forwardMostMove);
9430     if (appData.debugMode) {
9431         fprintf(debugFP, "Load Game\n");
9432     }
9433         DisplayBothClocks();
9434     }      
9435
9436     /* [HGM] server: flag to write setup moves in broadcast file as one */
9437     loadFlag = appData.suppressLoadMoves;
9438
9439     while (cm == Comment) {
9440         char *p;
9441         if (appData.debugMode) 
9442           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9443         p = yy_text;
9444         if (*p == '{' || *p == '[' || *p == '(') {
9445             p[strlen(p) - 1] = NULLCHAR;
9446             p++;
9447         }
9448         while (*p == '\n') p++;
9449         AppendComment(currentMove, p);
9450         yyboardindex = forwardMostMove;
9451         cm = (ChessMove) yylex();
9452     }
9453
9454     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9455         cm == WhiteWins || cm == BlackWins ||
9456         cm == GameIsDrawn || cm == GameUnfinished) {
9457         DisplayMessage("", _("No moves in game"));
9458         if (cmailMsgLoaded) {
9459             if (appData.debugMode)
9460               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9461             ClearHighlights();
9462             flipView = FALSE;
9463         }
9464         DrawPosition(FALSE, boards[currentMove]);
9465         DisplayBothClocks();
9466         gameMode = EditGame;
9467         ModeHighlight();
9468         gameFileFP = NULL;
9469         cmailOldMove = 0;
9470         return TRUE;
9471     }
9472
9473     // [HGM] PV info: routine tests if comment empty
9474     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9475         DisplayComment(currentMove - 1, commentList[currentMove]);
9476     }
9477     if (!matchMode && appData.timeDelay != 0) 
9478       DrawPosition(FALSE, boards[currentMove]);
9479
9480     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9481       programStats.ok_to_send = 1;
9482     }
9483
9484     /* if the first token after the PGN tags is a move
9485      * and not move number 1, retrieve it from the parser 
9486      */
9487     if (cm != MoveNumberOne)
9488         LoadGameOneMove(cm);
9489
9490     /* load the remaining moves from the file */
9491     while (LoadGameOneMove((ChessMove)0)) {
9492       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9493       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9494     }
9495
9496     /* rewind to the start of the game */
9497     currentMove = backwardMostMove;
9498
9499     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9500
9501     if (oldGameMode == AnalyzeFile ||
9502         oldGameMode == AnalyzeMode) {
9503       AnalyzeFileEvent();
9504     }
9505
9506     if (matchMode || appData.timeDelay == 0) {
9507       ToEndEvent();
9508       gameMode = EditGame;
9509       ModeHighlight();
9510     } else if (appData.timeDelay > 0) {
9511       AutoPlayGameLoop();
9512     }
9513
9514     if (appData.debugMode) 
9515         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9516
9517     loadFlag = 0; /* [HGM] true game starts */
9518     return TRUE;
9519 }
9520
9521 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9522 int
9523 ReloadPosition(offset)
9524      int offset;
9525 {
9526     int positionNumber = lastLoadPositionNumber + offset;
9527     if (lastLoadPositionFP == NULL) {
9528         DisplayError(_("No position has been loaded yet"), 0);
9529         return FALSE;
9530     }
9531     if (positionNumber <= 0) {
9532         DisplayError(_("Can't back up any further"), 0);
9533         return FALSE;
9534     }
9535     return LoadPosition(lastLoadPositionFP, positionNumber,
9536                         lastLoadPositionTitle);
9537 }
9538
9539 /* Load the nth position from the given file */
9540 int
9541 LoadPositionFromFile(filename, n, title)
9542      char *filename;
9543      int n;
9544      char *title;
9545 {
9546     FILE *f;
9547     char buf[MSG_SIZ];
9548
9549     if (strcmp(filename, "-") == 0) {
9550         return LoadPosition(stdin, n, "stdin");
9551     } else {
9552         f = fopen(filename, "rb");
9553         if (f == NULL) {
9554             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9555             DisplayError(buf, errno);
9556             return FALSE;
9557         } else {
9558             return LoadPosition(f, n, title);
9559         }
9560     }
9561 }
9562
9563 /* Load the nth position from the given open file, and close it */
9564 int
9565 LoadPosition(f, positionNumber, title)
9566      FILE *f;
9567      int positionNumber;
9568      char *title;
9569 {
9570     char *p, line[MSG_SIZ];
9571     Board initial_position;
9572     int i, j, fenMode, pn;
9573     
9574     if (gameMode == Training )
9575         SetTrainingModeOff();
9576
9577     if (gameMode != BeginningOfGame) {
9578         Reset(FALSE, TRUE);
9579     }
9580     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9581         fclose(lastLoadPositionFP);
9582     }
9583     if (positionNumber == 0) positionNumber = 1;
9584     lastLoadPositionFP = f;
9585     lastLoadPositionNumber = positionNumber;
9586     strcpy(lastLoadPositionTitle, title);
9587     if (first.pr == NoProc) {
9588       StartChessProgram(&first);
9589       InitChessProgram(&first, FALSE);
9590     }    
9591     pn = positionNumber;
9592     if (positionNumber < 0) {
9593         /* Negative position number means to seek to that byte offset */
9594         if (fseek(f, -positionNumber, 0) == -1) {
9595             DisplayError(_("Can't seek on position file"), 0);
9596             return FALSE;
9597         };
9598         pn = 1;
9599     } else {
9600         if (fseek(f, 0, 0) == -1) {
9601             if (f == lastLoadPositionFP ?
9602                 positionNumber == lastLoadPositionNumber + 1 :
9603                 positionNumber == 1) {
9604                 pn = 1;
9605             } else {
9606                 DisplayError(_("Can't seek on position file"), 0);
9607                 return FALSE;
9608             }
9609         }
9610     }
9611     /* See if this file is FEN or old-style xboard */
9612     if (fgets(line, MSG_SIZ, f) == NULL) {
9613         DisplayError(_("Position not found in file"), 0);
9614         return FALSE;
9615     }
9616     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9617     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9618
9619     if (pn >= 2) {
9620         if (fenMode || line[0] == '#') pn--;
9621         while (pn > 0) {
9622             /* skip positions before number pn */
9623             if (fgets(line, MSG_SIZ, f) == NULL) {
9624                 Reset(TRUE, TRUE);
9625                 DisplayError(_("Position not found in file"), 0);
9626                 return FALSE;
9627             }
9628             if (fenMode || line[0] == '#') pn--;
9629         }
9630     }
9631
9632     if (fenMode) {
9633         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9634             DisplayError(_("Bad FEN position in file"), 0);
9635             return FALSE;
9636         }
9637     } else {
9638         (void) fgets(line, MSG_SIZ, f);
9639         (void) fgets(line, MSG_SIZ, f);
9640     
9641         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9642             (void) fgets(line, MSG_SIZ, f);
9643             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9644                 if (*p == ' ')
9645                   continue;
9646                 initial_position[i][j++] = CharToPiece(*p);
9647             }
9648         }
9649     
9650         blackPlaysFirst = FALSE;
9651         if (!feof(f)) {
9652             (void) fgets(line, MSG_SIZ, f);
9653             if (strncmp(line, "black", strlen("black"))==0)
9654               blackPlaysFirst = TRUE;
9655         }
9656     }
9657     startedFromSetupPosition = TRUE;
9658     
9659     SendToProgram("force\n", &first);
9660     CopyBoard(boards[0], initial_position);
9661     if (blackPlaysFirst) {
9662         currentMove = forwardMostMove = backwardMostMove = 1;
9663         strcpy(moveList[0], "");
9664         strcpy(parseList[0], "");
9665         CopyBoard(boards[1], initial_position);
9666         DisplayMessage("", _("Black to play"));
9667     } else {
9668         currentMove = forwardMostMove = backwardMostMove = 0;
9669         DisplayMessage("", _("White to play"));
9670     }
9671           /* [HGM] copy FEN attributes as well */
9672           {   int i;
9673               initialRulePlies = FENrulePlies;
9674               epStatus[forwardMostMove] = FENepStatus;
9675               for( i=0; i< nrCastlingRights; i++ )
9676                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9677           }
9678     SendBoard(&first, forwardMostMove);
9679     if (appData.debugMode) {
9680 int i, j;
9681   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9682   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9683         fprintf(debugFP, "Load Position\n");
9684     }
9685
9686     if (positionNumber > 1) {
9687         sprintf(line, "%s %d", title, positionNumber);
9688         DisplayTitle(line);
9689     } else {
9690         DisplayTitle(title);
9691     }
9692     gameMode = EditGame;
9693     ModeHighlight();
9694     ResetClocks();
9695     timeRemaining[0][1] = whiteTimeRemaining;
9696     timeRemaining[1][1] = blackTimeRemaining;
9697     DrawPosition(FALSE, boards[currentMove]);
9698    
9699     return TRUE;
9700 }
9701
9702
9703 void
9704 CopyPlayerNameIntoFileName(dest, src)
9705      char **dest, *src;
9706 {
9707     while (*src != NULLCHAR && *src != ',') {
9708         if (*src == ' ') {
9709             *(*dest)++ = '_';
9710             src++;
9711         } else {
9712             *(*dest)++ = *src++;
9713         }
9714     }
9715 }
9716
9717 char *DefaultFileName(ext)
9718      char *ext;
9719 {
9720     static char def[MSG_SIZ];
9721     char *p;
9722
9723     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9724         p = def;
9725         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9726         *p++ = '-';
9727         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9728         *p++ = '.';
9729         strcpy(p, ext);
9730     } else {
9731         def[0] = NULLCHAR;
9732     }
9733     return def;
9734 }
9735
9736 /* Save the current game to the given file */
9737 int
9738 SaveGameToFile(filename, append)
9739      char *filename;
9740      int append;
9741 {
9742     FILE *f;
9743     char buf[MSG_SIZ];
9744
9745     if (strcmp(filename, "-") == 0) {
9746         return SaveGame(stdout, 0, NULL);
9747     } else {
9748         f = fopen(filename, append ? "a" : "w");
9749         if (f == NULL) {
9750             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9751             DisplayError(buf, errno);
9752             return FALSE;
9753         } else {
9754             return SaveGame(f, 0, NULL);
9755         }
9756     }
9757 }
9758
9759 char *
9760 SavePart(str)
9761      char *str;
9762 {
9763     static char buf[MSG_SIZ];
9764     char *p;
9765     
9766     p = strchr(str, ' ');
9767     if (p == NULL) return str;
9768     strncpy(buf, str, p - str);
9769     buf[p - str] = NULLCHAR;
9770     return buf;
9771 }
9772
9773 #define PGN_MAX_LINE 75
9774
9775 #define PGN_SIDE_WHITE  0
9776 #define PGN_SIDE_BLACK  1
9777
9778 /* [AS] */
9779 static int FindFirstMoveOutOfBook( int side )
9780 {
9781     int result = -1;
9782
9783     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9784         int index = backwardMostMove;
9785         int has_book_hit = 0;
9786
9787         if( (index % 2) != side ) {
9788             index++;
9789         }
9790
9791         while( index < forwardMostMove ) {
9792             /* Check to see if engine is in book */
9793             int depth = pvInfoList[index].depth;
9794             int score = pvInfoList[index].score;
9795             int in_book = 0;
9796
9797             if( depth <= 2 ) {
9798                 in_book = 1;
9799             }
9800             else if( score == 0 && depth == 63 ) {
9801                 in_book = 1; /* Zappa */
9802             }
9803             else if( score == 2 && depth == 99 ) {
9804                 in_book = 1; /* Abrok */
9805             }
9806
9807             has_book_hit += in_book;
9808
9809             if( ! in_book ) {
9810                 result = index;
9811
9812                 break;
9813             }
9814
9815             index += 2;
9816         }
9817     }
9818
9819     return result;
9820 }
9821
9822 /* [AS] */
9823 void GetOutOfBookInfo( char * buf )
9824 {
9825     int oob[2];
9826     int i;
9827     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9828
9829     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9830     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9831
9832     *buf = '\0';
9833
9834     if( oob[0] >= 0 || oob[1] >= 0 ) {
9835         for( i=0; i<2; i++ ) {
9836             int idx = oob[i];
9837
9838             if( idx >= 0 ) {
9839                 if( i > 0 && oob[0] >= 0 ) {
9840                     strcat( buf, "   " );
9841                 }
9842
9843                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9844                 sprintf( buf+strlen(buf), "%s%.2f", 
9845                     pvInfoList[idx].score >= 0 ? "+" : "",
9846                     pvInfoList[idx].score / 100.0 );
9847             }
9848         }
9849     }
9850 }
9851
9852 /* Save game in PGN style and close the file */
9853 int
9854 SaveGamePGN(f)
9855      FILE *f;
9856 {
9857     int i, offset, linelen, newblock;
9858     time_t tm;
9859 //    char *movetext;
9860     char numtext[32];
9861     int movelen, numlen, blank;
9862     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9863
9864     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9865     
9866     tm = time((time_t *) NULL);
9867     
9868     PrintPGNTags(f, &gameInfo);
9869     
9870     if (backwardMostMove > 0 || startedFromSetupPosition) {
9871         char *fen = PositionToFEN(backwardMostMove, NULL);
9872         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9873         fprintf(f, "\n{--------------\n");
9874         PrintPosition(f, backwardMostMove);
9875         fprintf(f, "--------------}\n");
9876         free(fen);
9877     }
9878     else {
9879         /* [AS] Out of book annotation */
9880         if( appData.saveOutOfBookInfo ) {
9881             char buf[64];
9882
9883             GetOutOfBookInfo( buf );
9884
9885             if( buf[0] != '\0' ) {
9886                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9887             }
9888         }
9889
9890         fprintf(f, "\n");
9891     }
9892
9893     i = backwardMostMove;
9894     linelen = 0;
9895     newblock = TRUE;
9896
9897     while (i < forwardMostMove) {
9898         /* Print comments preceding this move */
9899         if (commentList[i] != NULL) {
9900             if (linelen > 0) fprintf(f, "\n");
9901             fprintf(f, "{\n%s}\n", commentList[i]);
9902             linelen = 0;
9903             newblock = TRUE;
9904         }
9905
9906         /* Format move number */
9907         if ((i % 2) == 0) {
9908             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9909         } else {
9910             if (newblock) {
9911                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9912             } else {
9913                 numtext[0] = NULLCHAR;
9914             }
9915         }
9916         numlen = strlen(numtext);
9917         newblock = FALSE;
9918
9919         /* Print move number */
9920         blank = linelen > 0 && numlen > 0;
9921         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9922             fprintf(f, "\n");
9923             linelen = 0;
9924             blank = 0;
9925         }
9926         if (blank) {
9927             fprintf(f, " ");
9928             linelen++;
9929         }
9930         fprintf(f, "%s", numtext);
9931         linelen += numlen;
9932
9933         /* Get move */
9934         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9935         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9936
9937         /* Print move */
9938         blank = linelen > 0 && movelen > 0;
9939         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9940             fprintf(f, "\n");
9941             linelen = 0;
9942             blank = 0;
9943         }
9944         if (blank) {
9945             fprintf(f, " ");
9946             linelen++;
9947         }
9948         fprintf(f, "%s", move_buffer);
9949         linelen += movelen;
9950
9951         /* [AS] Add PV info if present */
9952         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9953             /* [HGM] add time */
9954             char buf[MSG_SIZ]; int seconds;
9955
9956             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9957
9958             if( seconds <= 0) buf[0] = 0; else
9959             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9960                 seconds = (seconds + 4)/10; // round to full seconds
9961                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9962                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9963             }
9964
9965             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9966                 pvInfoList[i].score >= 0 ? "+" : "",
9967                 pvInfoList[i].score / 100.0,
9968                 pvInfoList[i].depth,
9969                 buf );
9970
9971             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9972
9973             /* Print score/depth */
9974             blank = linelen > 0 && movelen > 0;
9975             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9976                 fprintf(f, "\n");
9977                 linelen = 0;
9978                 blank = 0;
9979             }
9980             if (blank) {
9981                 fprintf(f, " ");
9982                 linelen++;
9983             }
9984             fprintf(f, "%s", move_buffer);
9985             linelen += movelen;
9986         }
9987
9988         i++;
9989     }
9990     
9991     /* Start a new line */
9992     if (linelen > 0) fprintf(f, "\n");
9993
9994     /* Print comments after last move */
9995     if (commentList[i] != NULL) {
9996         fprintf(f, "{\n%s}\n", commentList[i]);
9997     }
9998
9999     /* Print result */
10000     if (gameInfo.resultDetails != NULL &&
10001         gameInfo.resultDetails[0] != NULLCHAR) {
10002         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10003                 PGNResult(gameInfo.result));
10004     } else {
10005         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10006     }
10007
10008     fclose(f);
10009     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10010     return TRUE;
10011 }
10012
10013 /* Save game in old style and close the file */
10014 int
10015 SaveGameOldStyle(f)
10016      FILE *f;
10017 {
10018     int i, offset;
10019     time_t tm;
10020     
10021     tm = time((time_t *) NULL);
10022     
10023     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10024     PrintOpponents(f);
10025     
10026     if (backwardMostMove > 0 || startedFromSetupPosition) {
10027         fprintf(f, "\n[--------------\n");
10028         PrintPosition(f, backwardMostMove);
10029         fprintf(f, "--------------]\n");
10030     } else {
10031         fprintf(f, "\n");
10032     }
10033
10034     i = backwardMostMove;
10035     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10036
10037     while (i < forwardMostMove) {
10038         if (commentList[i] != NULL) {
10039             fprintf(f, "[%s]\n", commentList[i]);
10040         }
10041
10042         if ((i % 2) == 1) {
10043             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10044             i++;
10045         } else {
10046             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10047             i++;
10048             if (commentList[i] != NULL) {
10049                 fprintf(f, "\n");
10050                 continue;
10051             }
10052             if (i >= forwardMostMove) {
10053                 fprintf(f, "\n");
10054                 break;
10055             }
10056             fprintf(f, "%s\n", parseList[i]);
10057             i++;
10058         }
10059     }
10060     
10061     if (commentList[i] != NULL) {
10062         fprintf(f, "[%s]\n", commentList[i]);
10063     }
10064
10065     /* This isn't really the old style, but it's close enough */
10066     if (gameInfo.resultDetails != NULL &&
10067         gameInfo.resultDetails[0] != NULLCHAR) {
10068         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10069                 gameInfo.resultDetails);
10070     } else {
10071         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10072     }
10073
10074     fclose(f);
10075     return TRUE;
10076 }
10077
10078 /* Save the current game to open file f and close the file */
10079 int
10080 SaveGame(f, dummy, dummy2)
10081      FILE *f;
10082      int dummy;
10083      char *dummy2;
10084 {
10085     if (gameMode == EditPosition) EditPositionDone(TRUE);
10086     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10087     if (appData.oldSaveStyle)
10088       return SaveGameOldStyle(f);
10089     else
10090       return SaveGamePGN(f);
10091 }
10092
10093 /* Save the current position to the given file */
10094 int
10095 SavePositionToFile(filename)
10096      char *filename;
10097 {
10098     FILE *f;
10099     char buf[MSG_SIZ];
10100
10101     if (strcmp(filename, "-") == 0) {
10102         return SavePosition(stdout, 0, NULL);
10103     } else {
10104         f = fopen(filename, "a");
10105         if (f == NULL) {
10106             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10107             DisplayError(buf, errno);
10108             return FALSE;
10109         } else {
10110             SavePosition(f, 0, NULL);
10111             return TRUE;
10112         }
10113     }
10114 }
10115
10116 /* Save the current position to the given open file and close the file */
10117 int
10118 SavePosition(f, dummy, dummy2)
10119      FILE *f;
10120      int dummy;
10121      char *dummy2;
10122 {
10123     time_t tm;
10124     char *fen;
10125
10126     if (gameMode == EditPosition) EditPositionDone(TRUE);
10127     if (appData.oldSaveStyle) {
10128         tm = time((time_t *) NULL);
10129     
10130         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10131         PrintOpponents(f);
10132         fprintf(f, "[--------------\n");
10133         PrintPosition(f, currentMove);
10134         fprintf(f, "--------------]\n");
10135     } else {
10136         fen = PositionToFEN(currentMove, NULL);
10137         fprintf(f, "%s\n", fen);
10138         free(fen);
10139     }
10140     fclose(f);
10141     return TRUE;
10142 }
10143
10144 void
10145 ReloadCmailMsgEvent(unregister)
10146      int unregister;
10147 {
10148 #if !WIN32
10149     static char *inFilename = NULL;
10150     static char *outFilename;
10151     int i;
10152     struct stat inbuf, outbuf;
10153     int status;
10154     
10155     /* Any registered moves are unregistered if unregister is set, */
10156     /* i.e. invoked by the signal handler */
10157     if (unregister) {
10158         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10159             cmailMoveRegistered[i] = FALSE;
10160             if (cmailCommentList[i] != NULL) {
10161                 free(cmailCommentList[i]);
10162                 cmailCommentList[i] = NULL;
10163             }
10164         }
10165         nCmailMovesRegistered = 0;
10166     }
10167
10168     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10169         cmailResult[i] = CMAIL_NOT_RESULT;
10170     }
10171     nCmailResults = 0;
10172
10173     if (inFilename == NULL) {
10174         /* Because the filenames are static they only get malloced once  */
10175         /* and they never get freed                                      */
10176         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10177         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10178
10179         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10180         sprintf(outFilename, "%s.out", appData.cmailGameName);
10181     }
10182     
10183     status = stat(outFilename, &outbuf);
10184     if (status < 0) {
10185         cmailMailedMove = FALSE;
10186     } else {
10187         status = stat(inFilename, &inbuf);
10188         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10189     }
10190     
10191     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10192        counts the games, notes how each one terminated, etc.
10193        
10194        It would be nice to remove this kludge and instead gather all
10195        the information while building the game list.  (And to keep it
10196        in the game list nodes instead of having a bunch of fixed-size
10197        parallel arrays.)  Note this will require getting each game's
10198        termination from the PGN tags, as the game list builder does
10199        not process the game moves.  --mann
10200        */
10201     cmailMsgLoaded = TRUE;
10202     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10203     
10204     /* Load first game in the file or popup game menu */
10205     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10206
10207 #endif /* !WIN32 */
10208     return;
10209 }
10210
10211 int
10212 RegisterMove()
10213 {
10214     FILE *f;
10215     char string[MSG_SIZ];
10216
10217     if (   cmailMailedMove
10218         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10219         return TRUE;            /* Allow free viewing  */
10220     }
10221
10222     /* Unregister move to ensure that we don't leave RegisterMove        */
10223     /* with the move registered when the conditions for registering no   */
10224     /* longer hold                                                       */
10225     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10226         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10227         nCmailMovesRegistered --;
10228
10229         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10230           {
10231               free(cmailCommentList[lastLoadGameNumber - 1]);
10232               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10233           }
10234     }
10235
10236     if (cmailOldMove == -1) {
10237         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10238         return FALSE;
10239     }
10240
10241     if (currentMove > cmailOldMove + 1) {
10242         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10243         return FALSE;
10244     }
10245
10246     if (currentMove < cmailOldMove) {
10247         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10248         return FALSE;
10249     }
10250
10251     if (forwardMostMove > currentMove) {
10252         /* Silently truncate extra moves */
10253         TruncateGame();
10254     }
10255
10256     if (   (currentMove == cmailOldMove + 1)
10257         || (   (currentMove == cmailOldMove)
10258             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10259                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10260         if (gameInfo.result != GameUnfinished) {
10261             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10262         }
10263
10264         if (commentList[currentMove] != NULL) {
10265             cmailCommentList[lastLoadGameNumber - 1]
10266               = StrSave(commentList[currentMove]);
10267         }
10268         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10269
10270         if (appData.debugMode)
10271           fprintf(debugFP, "Saving %s for game %d\n",
10272                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10273
10274         sprintf(string,
10275                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10276         
10277         f = fopen(string, "w");
10278         if (appData.oldSaveStyle) {
10279             SaveGameOldStyle(f); /* also closes the file */
10280             
10281             sprintf(string, "%s.pos.out", appData.cmailGameName);
10282             f = fopen(string, "w");
10283             SavePosition(f, 0, NULL); /* also closes the file */
10284         } else {
10285             fprintf(f, "{--------------\n");
10286             PrintPosition(f, currentMove);
10287             fprintf(f, "--------------}\n\n");
10288             
10289             SaveGame(f, 0, NULL); /* also closes the file*/
10290         }
10291         
10292         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10293         nCmailMovesRegistered ++;
10294     } else if (nCmailGames == 1) {
10295         DisplayError(_("You have not made a move yet"), 0);
10296         return FALSE;
10297     }
10298
10299     return TRUE;
10300 }
10301
10302 void
10303 MailMoveEvent()
10304 {
10305 #if !WIN32
10306     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10307     FILE *commandOutput;
10308     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10309     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10310     int nBuffers;
10311     int i;
10312     int archived;
10313     char *arcDir;
10314
10315     if (! cmailMsgLoaded) {
10316         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10317         return;
10318     }
10319
10320     if (nCmailGames == nCmailResults) {
10321         DisplayError(_("No unfinished games"), 0);
10322         return;
10323     }
10324
10325 #if CMAIL_PROHIBIT_REMAIL
10326     if (cmailMailedMove) {
10327         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);
10328         DisplayError(msg, 0);
10329         return;
10330     }
10331 #endif
10332
10333     if (! (cmailMailedMove || RegisterMove())) return;
10334     
10335     if (   cmailMailedMove
10336         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10337         sprintf(string, partCommandString,
10338                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10339         commandOutput = popen(string, "r");
10340
10341         if (commandOutput == NULL) {
10342             DisplayError(_("Failed to invoke cmail"), 0);
10343         } else {
10344             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10345                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10346             }
10347             if (nBuffers > 1) {
10348                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10349                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10350                 nBytes = MSG_SIZ - 1;
10351             } else {
10352                 (void) memcpy(msg, buffer, nBytes);
10353             }
10354             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10355
10356             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10357                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10358
10359                 archived = TRUE;
10360                 for (i = 0; i < nCmailGames; i ++) {
10361                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10362                         archived = FALSE;
10363                     }
10364                 }
10365                 if (   archived
10366                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10367                         != NULL)) {
10368                     sprintf(buffer, "%s/%s.%s.archive",
10369                             arcDir,
10370                             appData.cmailGameName,
10371                             gameInfo.date);
10372                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10373                     cmailMsgLoaded = FALSE;
10374                 }
10375             }
10376
10377             DisplayInformation(msg);
10378             pclose(commandOutput);
10379         }
10380     } else {
10381         if ((*cmailMsg) != '\0') {
10382             DisplayInformation(cmailMsg);
10383         }
10384     }
10385
10386     return;
10387 #endif /* !WIN32 */
10388 }
10389
10390 char *
10391 CmailMsg()
10392 {
10393 #if WIN32
10394     return NULL;
10395 #else
10396     int  prependComma = 0;
10397     char number[5];
10398     char string[MSG_SIZ];       /* Space for game-list */
10399     int  i;
10400     
10401     if (!cmailMsgLoaded) return "";
10402
10403     if (cmailMailedMove) {
10404         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10405     } else {
10406         /* Create a list of games left */
10407         sprintf(string, "[");
10408         for (i = 0; i < nCmailGames; i ++) {
10409             if (! (   cmailMoveRegistered[i]
10410                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10411                 if (prependComma) {
10412                     sprintf(number, ",%d", i + 1);
10413                 } else {
10414                     sprintf(number, "%d", i + 1);
10415                     prependComma = 1;
10416                 }
10417                 
10418                 strcat(string, number);
10419             }
10420         }
10421         strcat(string, "]");
10422
10423         if (nCmailMovesRegistered + nCmailResults == 0) {
10424             switch (nCmailGames) {
10425               case 1:
10426                 sprintf(cmailMsg,
10427                         _("Still need to make move for game\n"));
10428                 break;
10429                 
10430               case 2:
10431                 sprintf(cmailMsg,
10432                         _("Still need to make moves for both games\n"));
10433                 break;
10434                 
10435               default:
10436                 sprintf(cmailMsg,
10437                         _("Still need to make moves for all %d games\n"),
10438                         nCmailGames);
10439                 break;
10440             }
10441         } else {
10442             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10443               case 1:
10444                 sprintf(cmailMsg,
10445                         _("Still need to make a move for game %s\n"),
10446                         string);
10447                 break;
10448                 
10449               case 0:
10450                 if (nCmailResults == nCmailGames) {
10451                     sprintf(cmailMsg, _("No unfinished games\n"));
10452                 } else {
10453                     sprintf(cmailMsg, _("Ready to send mail\n"));
10454                 }
10455                 break;
10456                 
10457               default:
10458                 sprintf(cmailMsg,
10459                         _("Still need to make moves for games %s\n"),
10460                         string);
10461             }
10462         }
10463     }
10464     return cmailMsg;
10465 #endif /* WIN32 */
10466 }
10467
10468 void
10469 ResetGameEvent()
10470 {
10471     if (gameMode == Training)
10472       SetTrainingModeOff();
10473
10474     Reset(TRUE, TRUE);
10475     cmailMsgLoaded = FALSE;
10476     if (appData.icsActive) {
10477       SendToICS(ics_prefix);
10478       SendToICS("refresh\n");
10479     }
10480 }
10481
10482 void
10483 ExitEvent(status)
10484      int status;
10485 {
10486     exiting++;
10487     if (exiting > 2) {
10488       /* Give up on clean exit */
10489       exit(status);
10490     }
10491     if (exiting > 1) {
10492       /* Keep trying for clean exit */
10493       return;
10494     }
10495
10496     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10497
10498     if (telnetISR != NULL) {
10499       RemoveInputSource(telnetISR);
10500     }
10501     if (icsPR != NoProc) {
10502       DestroyChildProcess(icsPR, TRUE);
10503     }
10504
10505     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10506     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10507
10508     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10509     /* make sure this other one finishes before killing it!                  */
10510     if(endingGame) { int count = 0;
10511         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10512         while(endingGame && count++ < 10) DoSleep(1);
10513         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10514     }
10515
10516     /* Kill off chess programs */
10517     if (first.pr != NoProc) {
10518         ExitAnalyzeMode();
10519         
10520         DoSleep( appData.delayBeforeQuit );
10521         SendToProgram("quit\n", &first);
10522         DoSleep( appData.delayAfterQuit );
10523         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10524     }
10525     if (second.pr != NoProc) {
10526         DoSleep( appData.delayBeforeQuit );
10527         SendToProgram("quit\n", &second);
10528         DoSleep( appData.delayAfterQuit );
10529         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10530     }
10531     if (first.isr != NULL) {
10532         RemoveInputSource(first.isr);
10533     }
10534     if (second.isr != NULL) {
10535         RemoveInputSource(second.isr);
10536     }
10537
10538     ShutDownFrontEnd();
10539     exit(status);
10540 }
10541
10542 void
10543 PauseEvent()
10544 {
10545     if (appData.debugMode)
10546         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10547     if (pausing) {
10548         pausing = FALSE;
10549         ModeHighlight();
10550         if (gameMode == MachinePlaysWhite ||
10551             gameMode == MachinePlaysBlack) {
10552             StartClocks();
10553         } else {
10554             DisplayBothClocks();
10555         }
10556         if (gameMode == PlayFromGameFile) {
10557             if (appData.timeDelay >= 0) 
10558                 AutoPlayGameLoop();
10559         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10560             Reset(FALSE, TRUE);
10561             SendToICS(ics_prefix);
10562             SendToICS("refresh\n");
10563         } else if (currentMove < forwardMostMove) {
10564             ForwardInner(forwardMostMove);
10565         }
10566         pauseExamInvalid = FALSE;
10567     } else {
10568         switch (gameMode) {
10569           default:
10570             return;
10571           case IcsExamining:
10572             pauseExamForwardMostMove = forwardMostMove;
10573             pauseExamInvalid = FALSE;
10574             /* fall through */
10575           case IcsObserving:
10576           case IcsPlayingWhite:
10577           case IcsPlayingBlack:
10578             pausing = TRUE;
10579             ModeHighlight();
10580             return;
10581           case PlayFromGameFile:
10582             (void) StopLoadGameTimer();
10583             pausing = TRUE;
10584             ModeHighlight();
10585             break;
10586           case BeginningOfGame:
10587             if (appData.icsActive) return;
10588             /* else fall through */
10589           case MachinePlaysWhite:
10590           case MachinePlaysBlack:
10591           case TwoMachinesPlay:
10592             if (forwardMostMove == 0)
10593               return;           /* don't pause if no one has moved */
10594             if ((gameMode == MachinePlaysWhite &&
10595                  !WhiteOnMove(forwardMostMove)) ||
10596                 (gameMode == MachinePlaysBlack &&
10597                  WhiteOnMove(forwardMostMove))) {
10598                 StopClocks();
10599             }
10600             pausing = TRUE;
10601             ModeHighlight();
10602             break;
10603         }
10604     }
10605 }
10606
10607 void
10608 EditCommentEvent()
10609 {
10610     char title[MSG_SIZ];
10611
10612     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10613         strcpy(title, _("Edit comment"));
10614     } else {
10615         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10616                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10617                 parseList[currentMove - 1]);
10618     }
10619
10620     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10621 }
10622
10623
10624 void
10625 EditTagsEvent()
10626 {
10627     char *tags = PGNTags(&gameInfo);
10628     EditTagsPopUp(tags);
10629     free(tags);
10630 }
10631
10632 void
10633 AnalyzeModeEvent()
10634 {
10635     if (appData.noChessProgram || gameMode == AnalyzeMode)
10636       return;
10637
10638     if (gameMode != AnalyzeFile) {
10639         if (!appData.icsEngineAnalyze) {
10640                EditGameEvent();
10641                if (gameMode != EditGame) return;
10642         }
10643         ResurrectChessProgram();
10644         SendToProgram("analyze\n", &first);
10645         first.analyzing = TRUE;
10646         /*first.maybeThinking = TRUE;*/
10647         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10648         EngineOutputPopUp();
10649     }
10650     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10651     pausing = FALSE;
10652     ModeHighlight();
10653     SetGameInfo();
10654
10655     StartAnalysisClock();
10656     GetTimeMark(&lastNodeCountTime);
10657     lastNodeCount = 0;
10658 }
10659
10660 void
10661 AnalyzeFileEvent()
10662 {
10663     if (appData.noChessProgram || gameMode == AnalyzeFile)
10664       return;
10665
10666     if (gameMode != AnalyzeMode) {
10667         EditGameEvent();
10668         if (gameMode != EditGame) return;
10669         ResurrectChessProgram();
10670         SendToProgram("analyze\n", &first);
10671         first.analyzing = TRUE;
10672         /*first.maybeThinking = TRUE;*/
10673         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10674         EngineOutputPopUp();
10675     }
10676     gameMode = AnalyzeFile;
10677     pausing = FALSE;
10678     ModeHighlight();
10679     SetGameInfo();
10680
10681     StartAnalysisClock();
10682     GetTimeMark(&lastNodeCountTime);
10683     lastNodeCount = 0;
10684 }
10685
10686 void
10687 MachineWhiteEvent()
10688 {
10689     char buf[MSG_SIZ];
10690     char *bookHit = NULL;
10691
10692     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10693       return;
10694
10695
10696     if (gameMode == PlayFromGameFile || 
10697         gameMode == TwoMachinesPlay  || 
10698         gameMode == Training         || 
10699         gameMode == AnalyzeMode      || 
10700         gameMode == EndOfGame)
10701         EditGameEvent();
10702
10703     if (gameMode == EditPosition) 
10704         EditPositionDone(TRUE);
10705
10706     if (!WhiteOnMove(currentMove)) {
10707         DisplayError(_("It is not White's turn"), 0);
10708         return;
10709     }
10710   
10711     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10712       ExitAnalyzeMode();
10713
10714     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10715         gameMode == AnalyzeFile)
10716         TruncateGame();
10717
10718     ResurrectChessProgram();    /* in case it isn't running */
10719     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10720         gameMode = MachinePlaysWhite;
10721         ResetClocks();
10722     } else
10723     gameMode = MachinePlaysWhite;
10724     pausing = FALSE;
10725     ModeHighlight();
10726     SetGameInfo();
10727     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10728     DisplayTitle(buf);
10729     if (first.sendName) {
10730       sprintf(buf, "name %s\n", gameInfo.black);
10731       SendToProgram(buf, &first);
10732     }
10733     if (first.sendTime) {
10734       if (first.useColors) {
10735         SendToProgram("black\n", &first); /*gnu kludge*/
10736       }
10737       SendTimeRemaining(&first, TRUE);
10738     }
10739     if (first.useColors) {
10740       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10741     }
10742     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10743     SetMachineThinkingEnables();
10744     first.maybeThinking = TRUE;
10745     StartClocks();
10746     firstMove = FALSE;
10747
10748     if (appData.autoFlipView && !flipView) {
10749       flipView = !flipView;
10750       DrawPosition(FALSE, NULL);
10751       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10752     }
10753
10754     if(bookHit) { // [HGM] book: simulate book reply
10755         static char bookMove[MSG_SIZ]; // a bit generous?
10756
10757         programStats.nodes = programStats.depth = programStats.time = 
10758         programStats.score = programStats.got_only_move = 0;
10759         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10760
10761         strcpy(bookMove, "move ");
10762         strcat(bookMove, bookHit);
10763         HandleMachineMove(bookMove, &first);
10764     }
10765 }
10766
10767 void
10768 MachineBlackEvent()
10769 {
10770     char buf[MSG_SIZ];
10771    char *bookHit = NULL;
10772
10773     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10774         return;
10775
10776
10777     if (gameMode == PlayFromGameFile || 
10778         gameMode == TwoMachinesPlay  || 
10779         gameMode == Training         || 
10780         gameMode == AnalyzeMode      || 
10781         gameMode == EndOfGame)
10782         EditGameEvent();
10783
10784     if (gameMode == EditPosition) 
10785         EditPositionDone(TRUE);
10786
10787     if (WhiteOnMove(currentMove)) {
10788         DisplayError(_("It is not Black's turn"), 0);
10789         return;
10790     }
10791     
10792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10793       ExitAnalyzeMode();
10794
10795     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10796         gameMode == AnalyzeFile)
10797         TruncateGame();
10798
10799     ResurrectChessProgram();    /* in case it isn't running */
10800     gameMode = MachinePlaysBlack;
10801     pausing = FALSE;
10802     ModeHighlight();
10803     SetGameInfo();
10804     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10805     DisplayTitle(buf);
10806     if (first.sendName) {
10807       sprintf(buf, "name %s\n", gameInfo.white);
10808       SendToProgram(buf, &first);
10809     }
10810     if (first.sendTime) {
10811       if (first.useColors) {
10812         SendToProgram("white\n", &first); /*gnu kludge*/
10813       }
10814       SendTimeRemaining(&first, FALSE);
10815     }
10816     if (first.useColors) {
10817       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10818     }
10819     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10820     SetMachineThinkingEnables();
10821     first.maybeThinking = TRUE;
10822     StartClocks();
10823
10824     if (appData.autoFlipView && flipView) {
10825       flipView = !flipView;
10826       DrawPosition(FALSE, NULL);
10827       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10828     }
10829     if(bookHit) { // [HGM] book: simulate book reply
10830         static char bookMove[MSG_SIZ]; // a bit generous?
10831
10832         programStats.nodes = programStats.depth = programStats.time = 
10833         programStats.score = programStats.got_only_move = 0;
10834         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10835
10836         strcpy(bookMove, "move ");
10837         strcat(bookMove, bookHit);
10838         HandleMachineMove(bookMove, &first);
10839     }
10840 }
10841
10842
10843 void
10844 DisplayTwoMachinesTitle()
10845 {
10846     char buf[MSG_SIZ];
10847     if (appData.matchGames > 0) {
10848         if (first.twoMachinesColor[0] == 'w') {
10849             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10850                     gameInfo.white, gameInfo.black,
10851                     first.matchWins, second.matchWins,
10852                     matchGame - 1 - (first.matchWins + second.matchWins));
10853         } else {
10854             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10855                     gameInfo.white, gameInfo.black,
10856                     second.matchWins, first.matchWins,
10857                     matchGame - 1 - (first.matchWins + second.matchWins));
10858         }
10859     } else {
10860         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10861     }
10862     DisplayTitle(buf);
10863 }
10864
10865 void
10866 TwoMachinesEvent P((void))
10867 {
10868     int i;
10869     char buf[MSG_SIZ];
10870     ChessProgramState *onmove;
10871     char *bookHit = NULL;
10872     
10873     if (appData.noChessProgram) return;
10874
10875     switch (gameMode) {
10876       case TwoMachinesPlay:
10877         return;
10878       case MachinePlaysWhite:
10879       case MachinePlaysBlack:
10880         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10881             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10882             return;
10883         }
10884         /* fall through */
10885       case BeginningOfGame:
10886       case PlayFromGameFile:
10887       case EndOfGame:
10888         EditGameEvent();
10889         if (gameMode != EditGame) return;
10890         break;
10891       case EditPosition:
10892         EditPositionDone(TRUE);
10893         break;
10894       case AnalyzeMode:
10895       case AnalyzeFile:
10896         ExitAnalyzeMode();
10897         break;
10898       case EditGame:
10899       default:
10900         break;
10901     }
10902
10903     forwardMostMove = currentMove;
10904     ResurrectChessProgram();    /* in case first program isn't running */
10905
10906     if (second.pr == NULL) {
10907         StartChessProgram(&second);
10908         if (second.protocolVersion == 1) {
10909           TwoMachinesEventIfReady();
10910         } else {
10911           /* kludge: allow timeout for initial "feature" command */
10912           FreezeUI();
10913           DisplayMessage("", _("Starting second chess program"));
10914           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10915         }
10916         return;
10917     }
10918     DisplayMessage("", "");
10919     InitChessProgram(&second, FALSE);
10920     SendToProgram("force\n", &second);
10921     if (startedFromSetupPosition) {
10922         SendBoard(&second, backwardMostMove);
10923     if (appData.debugMode) {
10924         fprintf(debugFP, "Two Machines\n");
10925     }
10926     }
10927     for (i = backwardMostMove; i < forwardMostMove; i++) {
10928         SendMoveToProgram(i, &second);
10929     }
10930
10931     gameMode = TwoMachinesPlay;
10932     pausing = FALSE;
10933     ModeHighlight();
10934     SetGameInfo();
10935     DisplayTwoMachinesTitle();
10936     firstMove = TRUE;
10937     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10938         onmove = &first;
10939     } else {
10940         onmove = &second;
10941     }
10942
10943     SendToProgram(first.computerString, &first);
10944     if (first.sendName) {
10945       sprintf(buf, "name %s\n", second.tidy);
10946       SendToProgram(buf, &first);
10947     }
10948     SendToProgram(second.computerString, &second);
10949     if (second.sendName) {
10950       sprintf(buf, "name %s\n", first.tidy);
10951       SendToProgram(buf, &second);
10952     }
10953
10954     ResetClocks();
10955     if (!first.sendTime || !second.sendTime) {
10956         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10957         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10958     }
10959     if (onmove->sendTime) {
10960       if (onmove->useColors) {
10961         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10962       }
10963       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10964     }
10965     if (onmove->useColors) {
10966       SendToProgram(onmove->twoMachinesColor, onmove);
10967     }
10968     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10969 //    SendToProgram("go\n", onmove);
10970     onmove->maybeThinking = TRUE;
10971     SetMachineThinkingEnables();
10972
10973     StartClocks();
10974
10975     if(bookHit) { // [HGM] book: simulate book reply
10976         static char bookMove[MSG_SIZ]; // a bit generous?
10977
10978         programStats.nodes = programStats.depth = programStats.time = 
10979         programStats.score = programStats.got_only_move = 0;
10980         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10981
10982         strcpy(bookMove, "move ");
10983         strcat(bookMove, bookHit);
10984         savedMessage = bookMove; // args for deferred call
10985         savedState = onmove;
10986         ScheduleDelayedEvent(DeferredBookMove, 1);
10987     }
10988 }
10989
10990 void
10991 TrainingEvent()
10992 {
10993     if (gameMode == Training) {
10994       SetTrainingModeOff();
10995       gameMode = PlayFromGameFile;
10996       DisplayMessage("", _("Training mode off"));
10997     } else {
10998       gameMode = Training;
10999       animateTraining = appData.animate;
11000
11001       /* make sure we are not already at the end of the game */
11002       if (currentMove < forwardMostMove) {
11003         SetTrainingModeOn();
11004         DisplayMessage("", _("Training mode on"));
11005       } else {
11006         gameMode = PlayFromGameFile;
11007         DisplayError(_("Already at end of game"), 0);
11008       }
11009     }
11010     ModeHighlight();
11011 }
11012
11013 void
11014 IcsClientEvent()
11015 {
11016     if (!appData.icsActive) return;
11017     switch (gameMode) {
11018       case IcsPlayingWhite:
11019       case IcsPlayingBlack:
11020       case IcsObserving:
11021       case IcsIdle:
11022       case BeginningOfGame:
11023       case IcsExamining:
11024         return;
11025
11026       case EditGame:
11027         break;
11028
11029       case EditPosition:
11030         EditPositionDone(TRUE);
11031         break;
11032
11033       case AnalyzeMode:
11034       case AnalyzeFile:
11035         ExitAnalyzeMode();
11036         break;
11037         
11038       default:
11039         EditGameEvent();
11040         break;
11041     }
11042
11043     gameMode = IcsIdle;
11044     ModeHighlight();
11045     return;
11046 }
11047
11048
11049 void
11050 EditGameEvent()
11051 {
11052     int i;
11053
11054     switch (gameMode) {
11055       case Training:
11056         SetTrainingModeOff();
11057         break;
11058       case MachinePlaysWhite:
11059       case MachinePlaysBlack:
11060       case BeginningOfGame:
11061         SendToProgram("force\n", &first);
11062         SetUserThinkingEnables();
11063         break;
11064       case PlayFromGameFile:
11065         (void) StopLoadGameTimer();
11066         if (gameFileFP != NULL) {
11067             gameFileFP = NULL;
11068         }
11069         break;
11070       case EditPosition:
11071         EditPositionDone(TRUE);
11072         break;
11073       case AnalyzeMode:
11074       case AnalyzeFile:
11075         ExitAnalyzeMode();
11076         SendToProgram("force\n", &first);
11077         break;
11078       case TwoMachinesPlay:
11079         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11080         ResurrectChessProgram();
11081         SetUserThinkingEnables();
11082         break;
11083       case EndOfGame:
11084         ResurrectChessProgram();
11085         break;
11086       case IcsPlayingBlack:
11087       case IcsPlayingWhite:
11088         DisplayError(_("Warning: You are still playing a game"), 0);
11089         break;
11090       case IcsObserving:
11091         DisplayError(_("Warning: You are still observing a game"), 0);
11092         break;
11093       case IcsExamining:
11094         DisplayError(_("Warning: You are still examining a game"), 0);
11095         break;
11096       case IcsIdle:
11097         break;
11098       case EditGame:
11099       default:
11100         return;
11101     }
11102     
11103     pausing = FALSE;
11104     StopClocks();
11105     first.offeredDraw = second.offeredDraw = 0;
11106
11107     if (gameMode == PlayFromGameFile) {
11108         whiteTimeRemaining = timeRemaining[0][currentMove];
11109         blackTimeRemaining = timeRemaining[1][currentMove];
11110         DisplayTitle("");
11111     }
11112
11113     if (gameMode == MachinePlaysWhite ||
11114         gameMode == MachinePlaysBlack ||
11115         gameMode == TwoMachinesPlay ||
11116         gameMode == EndOfGame) {
11117         i = forwardMostMove;
11118         while (i > currentMove) {
11119             SendToProgram("undo\n", &first);
11120             i--;
11121         }
11122         whiteTimeRemaining = timeRemaining[0][currentMove];
11123         blackTimeRemaining = timeRemaining[1][currentMove];
11124         DisplayBothClocks();
11125         if (whiteFlag || blackFlag) {
11126             whiteFlag = blackFlag = 0;
11127         }
11128         DisplayTitle("");
11129     }           
11130     
11131     gameMode = EditGame;
11132     ModeHighlight();
11133     SetGameInfo();
11134 }
11135
11136
11137 void
11138 EditPositionEvent()
11139 {
11140     if (gameMode == EditPosition) {
11141         EditGameEvent();
11142         return;
11143     }
11144     
11145     EditGameEvent();
11146     if (gameMode != EditGame) return;
11147     
11148     gameMode = EditPosition;
11149     ModeHighlight();
11150     SetGameInfo();
11151     if (currentMove > 0)
11152       CopyBoard(boards[0], boards[currentMove]);
11153     
11154     blackPlaysFirst = !WhiteOnMove(currentMove);
11155     ResetClocks();
11156     currentMove = forwardMostMove = backwardMostMove = 0;
11157     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11158     DisplayMove(-1);
11159 }
11160
11161 void
11162 ExitAnalyzeMode()
11163 {
11164     /* [DM] icsEngineAnalyze - possible call from other functions */
11165     if (appData.icsEngineAnalyze) {
11166         appData.icsEngineAnalyze = FALSE;
11167
11168         DisplayMessage("",_("Close ICS engine analyze..."));
11169     }
11170     if (first.analysisSupport && first.analyzing) {
11171       SendToProgram("exit\n", &first);
11172       first.analyzing = FALSE;
11173     }
11174     thinkOutput[0] = NULLCHAR;
11175 }
11176
11177 void
11178 EditPositionDone(Boolean fakeRights)
11179 {
11180     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11181
11182     startedFromSetupPosition = TRUE;
11183     InitChessProgram(&first, FALSE);
11184     if(fakeRights)  
11185       { /* don't do this if we just pasted FEN */
11186         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11187         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11188           {
11189             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11190             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11191           } 
11192         else 
11193           castlingRights[0][2] = -1;
11194         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11195           {
11196             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11197             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11198           } 
11199         else 
11200           castlingRights[0][5] = -1;
11201       }
11202     SendToProgram("force\n", &first);
11203     if (blackPlaysFirst) {
11204         strcpy(moveList[0], "");
11205         strcpy(parseList[0], "");
11206         currentMove = forwardMostMove = backwardMostMove = 1;
11207         CopyBoard(boards[1], boards[0]);
11208         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11209         { int i;
11210           epStatus[1] = epStatus[0];
11211           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11212         }
11213     } else {
11214         currentMove = forwardMostMove = backwardMostMove = 0;
11215     }
11216     SendBoard(&first, forwardMostMove);
11217     if (appData.debugMode) {
11218         fprintf(debugFP, "EditPosDone\n");
11219     }
11220     DisplayTitle("");
11221     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11222     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11223     gameMode = EditGame;
11224     ModeHighlight();
11225     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11226     ClearHighlights(); /* [AS] */
11227 }
11228
11229 /* Pause for `ms' milliseconds */
11230 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11231 void
11232 TimeDelay(ms)
11233      long ms;
11234 {
11235     TimeMark m1, m2;
11236
11237     GetTimeMark(&m1);
11238     do {
11239         GetTimeMark(&m2);
11240     } while (SubtractTimeMarks(&m2, &m1) < ms);
11241 }
11242
11243 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11244 void
11245 SendMultiLineToICS(buf)
11246      char *buf;
11247 {
11248     char temp[MSG_SIZ+1], *p;
11249     int len;
11250
11251     len = strlen(buf);
11252     if (len > MSG_SIZ)
11253       len = MSG_SIZ;
11254   
11255     strncpy(temp, buf, len);
11256     temp[len] = 0;
11257
11258     p = temp;
11259     while (*p) {
11260         if (*p == '\n' || *p == '\r')
11261           *p = ' ';
11262         ++p;
11263     }
11264
11265     strcat(temp, "\n");
11266     SendToICS(temp);
11267     SendToPlayer(temp, strlen(temp));
11268 }
11269
11270 void
11271 SetWhiteToPlayEvent()
11272 {
11273     if (gameMode == EditPosition) {
11274         blackPlaysFirst = FALSE;
11275         DisplayBothClocks();    /* works because currentMove is 0 */
11276     } else if (gameMode == IcsExamining) {
11277         SendToICS(ics_prefix);
11278         SendToICS("tomove white\n");
11279     }
11280 }
11281
11282 void
11283 SetBlackToPlayEvent()
11284 {
11285     if (gameMode == EditPosition) {
11286         blackPlaysFirst = TRUE;
11287         currentMove = 1;        /* kludge */
11288         DisplayBothClocks();
11289         currentMove = 0;
11290     } else if (gameMode == IcsExamining) {
11291         SendToICS(ics_prefix);
11292         SendToICS("tomove black\n");
11293     }
11294 }
11295
11296 void
11297 EditPositionMenuEvent(selection, x, y)
11298      ChessSquare selection;
11299      int x, y;
11300 {
11301     char buf[MSG_SIZ];
11302     ChessSquare piece = boards[0][y][x];
11303
11304     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11305
11306     switch (selection) {
11307       case ClearBoard:
11308         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11309             SendToICS(ics_prefix);
11310             SendToICS("bsetup clear\n");
11311         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11312             SendToICS(ics_prefix);
11313             SendToICS("clearboard\n");
11314         } else {
11315             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11316                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11317                 for (y = 0; y < BOARD_HEIGHT; y++) {
11318                     if (gameMode == IcsExamining) {
11319                         if (boards[currentMove][y][x] != EmptySquare) {
11320                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11321                                     AAA + x, ONE + y);
11322                             SendToICS(buf);
11323                         }
11324                     } else {
11325                         boards[0][y][x] = p;
11326                     }
11327                 }
11328             }
11329         }
11330         if (gameMode == EditPosition) {
11331             DrawPosition(FALSE, boards[0]);
11332         }
11333         break;
11334
11335       case WhitePlay:
11336         SetWhiteToPlayEvent();
11337         break;
11338
11339       case BlackPlay:
11340         SetBlackToPlayEvent();
11341         break;
11342
11343       case EmptySquare:
11344         if (gameMode == IcsExamining) {
11345             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11346             SendToICS(buf);
11347         } else {
11348             boards[0][y][x] = EmptySquare;
11349             DrawPosition(FALSE, boards[0]);
11350         }
11351         break;
11352
11353       case PromotePiece:
11354         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11355            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11356             selection = (ChessSquare) (PROMOTED piece);
11357         } else if(piece == EmptySquare) selection = WhiteSilver;
11358         else selection = (ChessSquare)((int)piece - 1);
11359         goto defaultlabel;
11360
11361       case DemotePiece:
11362         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11363            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11364             selection = (ChessSquare) (DEMOTED piece);
11365         } else if(piece == EmptySquare) selection = BlackSilver;
11366         else selection = (ChessSquare)((int)piece + 1);       
11367         goto defaultlabel;
11368
11369       case WhiteQueen:
11370       case BlackQueen:
11371         if(gameInfo.variant == VariantShatranj ||
11372            gameInfo.variant == VariantXiangqi  ||
11373            gameInfo.variant == VariantCourier    )
11374             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11375         goto defaultlabel;
11376
11377       case WhiteKing:
11378       case BlackKing:
11379         if(gameInfo.variant == VariantXiangqi)
11380             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11381         if(gameInfo.variant == VariantKnightmate)
11382             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11383       default:
11384         defaultlabel:
11385         if (gameMode == IcsExamining) {
11386             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11387                     PieceToChar(selection), AAA + x, ONE + y);
11388             SendToICS(buf);
11389         } else {
11390             boards[0][y][x] = selection;
11391             DrawPosition(FALSE, boards[0]);
11392         }
11393         break;
11394     }
11395 }
11396
11397
11398 void
11399 DropMenuEvent(selection, x, y)
11400      ChessSquare selection;
11401      int x, y;
11402 {
11403     ChessMove moveType;
11404
11405     switch (gameMode) {
11406       case IcsPlayingWhite:
11407       case MachinePlaysBlack:
11408         if (!WhiteOnMove(currentMove)) {
11409             DisplayMoveError(_("It is Black's turn"));
11410             return;
11411         }
11412         moveType = WhiteDrop;
11413         break;
11414       case IcsPlayingBlack:
11415       case MachinePlaysWhite:
11416         if (WhiteOnMove(currentMove)) {
11417             DisplayMoveError(_("It is White's turn"));
11418             return;
11419         }
11420         moveType = BlackDrop;
11421         break;
11422       case EditGame:
11423         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11424         break;
11425       default:
11426         return;
11427     }
11428
11429     if (moveType == BlackDrop && selection < BlackPawn) {
11430       selection = (ChessSquare) ((int) selection
11431                                  + (int) BlackPawn - (int) WhitePawn);
11432     }
11433     if (boards[currentMove][y][x] != EmptySquare) {
11434         DisplayMoveError(_("That square is occupied"));
11435         return;
11436     }
11437
11438     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11439 }
11440
11441 void
11442 AcceptEvent()
11443 {
11444     /* Accept a pending offer of any kind from opponent */
11445     
11446     if (appData.icsActive) {
11447         SendToICS(ics_prefix);
11448         SendToICS("accept\n");
11449     } else if (cmailMsgLoaded) {
11450         if (currentMove == cmailOldMove &&
11451             commentList[cmailOldMove] != NULL &&
11452             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11453                    "Black offers a draw" : "White offers a draw")) {
11454             TruncateGame();
11455             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11456             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11457         } else {
11458             DisplayError(_("There is no pending offer on this move"), 0);
11459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11460         }
11461     } else {
11462         /* Not used for offers from chess program */
11463     }
11464 }
11465
11466 void
11467 DeclineEvent()
11468 {
11469     /* Decline a pending offer of any kind from opponent */
11470     
11471     if (appData.icsActive) {
11472         SendToICS(ics_prefix);
11473         SendToICS("decline\n");
11474     } else if (cmailMsgLoaded) {
11475         if (currentMove == cmailOldMove &&
11476             commentList[cmailOldMove] != NULL &&
11477             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11478                    "Black offers a draw" : "White offers a draw")) {
11479 #ifdef NOTDEF
11480             AppendComment(cmailOldMove, "Draw declined");
11481             DisplayComment(cmailOldMove - 1, "Draw declined");
11482 #endif /*NOTDEF*/
11483         } else {
11484             DisplayError(_("There is no pending offer on this move"), 0);
11485         }
11486     } else {
11487         /* Not used for offers from chess program */
11488     }
11489 }
11490
11491 void
11492 RematchEvent()
11493 {
11494     /* Issue ICS rematch command */
11495     if (appData.icsActive) {
11496         SendToICS(ics_prefix);
11497         SendToICS("rematch\n");
11498     }
11499 }
11500
11501 void
11502 CallFlagEvent()
11503 {
11504     /* Call your opponent's flag (claim a win on time) */
11505     if (appData.icsActive) {
11506         SendToICS(ics_prefix);
11507         SendToICS("flag\n");
11508     } else {
11509         switch (gameMode) {
11510           default:
11511             return;
11512           case MachinePlaysWhite:
11513             if (whiteFlag) {
11514                 if (blackFlag)
11515                   GameEnds(GameIsDrawn, "Both players ran out of time",
11516                            GE_PLAYER);
11517                 else
11518                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11519             } else {
11520                 DisplayError(_("Your opponent is not out of time"), 0);
11521             }
11522             break;
11523           case MachinePlaysBlack:
11524             if (blackFlag) {
11525                 if (whiteFlag)
11526                   GameEnds(GameIsDrawn, "Both players ran out of time",
11527                            GE_PLAYER);
11528                 else
11529                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11530             } else {
11531                 DisplayError(_("Your opponent is not out of time"), 0);
11532             }
11533             break;
11534         }
11535     }
11536 }
11537
11538 void
11539 DrawEvent()
11540 {
11541     /* Offer draw or accept pending draw offer from opponent */
11542     
11543     if (appData.icsActive) {
11544         /* Note: tournament rules require draw offers to be
11545            made after you make your move but before you punch
11546            your clock.  Currently ICS doesn't let you do that;
11547            instead, you immediately punch your clock after making
11548            a move, but you can offer a draw at any time. */
11549         
11550         SendToICS(ics_prefix);
11551         SendToICS("draw\n");
11552     } else if (cmailMsgLoaded) {
11553         if (currentMove == cmailOldMove &&
11554             commentList[cmailOldMove] != NULL &&
11555             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11556                    "Black offers a draw" : "White offers a draw")) {
11557             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11558             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11559         } else if (currentMove == cmailOldMove + 1) {
11560             char *offer = WhiteOnMove(cmailOldMove) ?
11561               "White offers a draw" : "Black offers a draw";
11562             AppendComment(currentMove, offer);
11563             DisplayComment(currentMove - 1, offer);
11564             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11565         } else {
11566             DisplayError(_("You must make your move before offering a draw"), 0);
11567             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11568         }
11569     } else if (first.offeredDraw) {
11570         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11571     } else {
11572         if (first.sendDrawOffers) {
11573             SendToProgram("draw\n", &first);
11574             userOfferedDraw = TRUE;
11575         }
11576     }
11577 }
11578
11579 void
11580 AdjournEvent()
11581 {
11582     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11583     
11584     if (appData.icsActive) {
11585         SendToICS(ics_prefix);
11586         SendToICS("adjourn\n");
11587     } else {
11588         /* Currently GNU Chess doesn't offer or accept Adjourns */
11589     }
11590 }
11591
11592
11593 void
11594 AbortEvent()
11595 {
11596     /* Offer Abort or accept pending Abort offer from opponent */
11597     
11598     if (appData.icsActive) {
11599         SendToICS(ics_prefix);
11600         SendToICS("abort\n");
11601     } else {
11602         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11603     }
11604 }
11605
11606 void
11607 ResignEvent()
11608 {
11609     /* Resign.  You can do this even if it's not your turn. */
11610     
11611     if (appData.icsActive) {
11612         SendToICS(ics_prefix);
11613         SendToICS("resign\n");
11614     } else {
11615         switch (gameMode) {
11616           case MachinePlaysWhite:
11617             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11618             break;
11619           case MachinePlaysBlack:
11620             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11621             break;
11622           case EditGame:
11623             if (cmailMsgLoaded) {
11624                 TruncateGame();
11625                 if (WhiteOnMove(cmailOldMove)) {
11626                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11627                 } else {
11628                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11629                 }
11630                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11631             }
11632             break;
11633           default:
11634             break;
11635         }
11636     }
11637 }
11638
11639
11640 void
11641 StopObservingEvent()
11642 {
11643     /* Stop observing current games */
11644     SendToICS(ics_prefix);
11645     SendToICS("unobserve\n");
11646 }
11647
11648 void
11649 StopExaminingEvent()
11650 {
11651     /* Stop observing current game */
11652     SendToICS(ics_prefix);
11653     SendToICS("unexamine\n");
11654 }
11655
11656 void
11657 ForwardInner(target)
11658      int target;
11659 {
11660     int limit;
11661
11662     if (appData.debugMode)
11663         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11664                 target, currentMove, forwardMostMove);
11665
11666     if (gameMode == EditPosition)
11667       return;
11668
11669     if (gameMode == PlayFromGameFile && !pausing)
11670       PauseEvent();
11671     
11672     if (gameMode == IcsExamining && pausing)
11673       limit = pauseExamForwardMostMove;
11674     else
11675       limit = forwardMostMove;
11676     
11677     if (target > limit) target = limit;
11678
11679     if (target > 0 && moveList[target - 1][0]) {
11680         int fromX, fromY, toX, toY;
11681         toX = moveList[target - 1][2] - AAA;
11682         toY = moveList[target - 1][3] - ONE;
11683         if (moveList[target - 1][1] == '@') {
11684             if (appData.highlightLastMove) {
11685                 SetHighlights(-1, -1, toX, toY);
11686             }
11687         } else {
11688             fromX = moveList[target - 1][0] - AAA;
11689             fromY = moveList[target - 1][1] - ONE;
11690             if (target == currentMove + 1) {
11691                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11692             }
11693             if (appData.highlightLastMove) {
11694                 SetHighlights(fromX, fromY, toX, toY);
11695             }
11696         }
11697     }
11698     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11699         gameMode == Training || gameMode == PlayFromGameFile || 
11700         gameMode == AnalyzeFile) {
11701         while (currentMove < target) {
11702             SendMoveToProgram(currentMove++, &first);
11703         }
11704     } else {
11705         currentMove = target;
11706     }
11707     
11708     if (gameMode == EditGame || gameMode == EndOfGame) {
11709         whiteTimeRemaining = timeRemaining[0][currentMove];
11710         blackTimeRemaining = timeRemaining[1][currentMove];
11711     }
11712     DisplayBothClocks();
11713     DisplayMove(currentMove - 1);
11714     DrawPosition(FALSE, boards[currentMove]);
11715     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11716     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11717         DisplayComment(currentMove - 1, commentList[currentMove]);
11718     }
11719 }
11720
11721
11722 void
11723 ForwardEvent()
11724 {
11725     if (gameMode == IcsExamining && !pausing) {
11726         SendToICS(ics_prefix);
11727         SendToICS("forward\n");
11728     } else {
11729         ForwardInner(currentMove + 1);
11730     }
11731 }
11732
11733 void
11734 ToEndEvent()
11735 {
11736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11737         /* to optimze, we temporarily turn off analysis mode while we feed
11738          * the remaining moves to the engine. Otherwise we get analysis output
11739          * after each move.
11740          */ 
11741         if (first.analysisSupport) {
11742           SendToProgram("exit\nforce\n", &first);
11743           first.analyzing = FALSE;
11744         }
11745     }
11746         
11747     if (gameMode == IcsExamining && !pausing) {
11748         SendToICS(ics_prefix);
11749         SendToICS("forward 999999\n");
11750     } else {
11751         ForwardInner(forwardMostMove);
11752     }
11753
11754     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11755         /* we have fed all the moves, so reactivate analysis mode */
11756         SendToProgram("analyze\n", &first);
11757         first.analyzing = TRUE;
11758         /*first.maybeThinking = TRUE;*/
11759         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11760     }
11761 }
11762
11763 void
11764 BackwardInner(target)
11765      int target;
11766 {
11767     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11768
11769     if (appData.debugMode)
11770         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11771                 target, currentMove, forwardMostMove);
11772
11773     if (gameMode == EditPosition) return;
11774     if (currentMove <= backwardMostMove) {
11775         ClearHighlights();
11776         DrawPosition(full_redraw, boards[currentMove]);
11777         return;
11778     }
11779     if (gameMode == PlayFromGameFile && !pausing)
11780       PauseEvent();
11781     
11782     if (moveList[target][0]) {
11783         int fromX, fromY, toX, toY;
11784         toX = moveList[target][2] - AAA;
11785         toY = moveList[target][3] - ONE;
11786         if (moveList[target][1] == '@') {
11787             if (appData.highlightLastMove) {
11788                 SetHighlights(-1, -1, toX, toY);
11789             }
11790         } else {
11791             fromX = moveList[target][0] - AAA;
11792             fromY = moveList[target][1] - ONE;
11793             if (target == currentMove - 1) {
11794                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11795             }
11796             if (appData.highlightLastMove) {
11797                 SetHighlights(fromX, fromY, toX, toY);
11798             }
11799         }
11800     }
11801     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11802         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11803         while (currentMove > target) {
11804             SendToProgram("undo\n", &first);
11805             currentMove--;
11806         }
11807     } else {
11808         currentMove = target;
11809     }
11810     
11811     if (gameMode == EditGame || gameMode == EndOfGame) {
11812         whiteTimeRemaining = timeRemaining[0][currentMove];
11813         blackTimeRemaining = timeRemaining[1][currentMove];
11814     }
11815     DisplayBothClocks();
11816     DisplayMove(currentMove - 1);
11817     DrawPosition(full_redraw, boards[currentMove]);
11818     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11819     // [HGM] PV info: routine tests if comment empty
11820     DisplayComment(currentMove - 1, commentList[currentMove]);
11821 }
11822
11823 void
11824 BackwardEvent()
11825 {
11826     if (gameMode == IcsExamining && !pausing) {
11827         SendToICS(ics_prefix);
11828         SendToICS("backward\n");
11829     } else {
11830         BackwardInner(currentMove - 1);
11831     }
11832 }
11833
11834 void
11835 ToStartEvent()
11836 {
11837     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11838         /* to optimize, we temporarily turn off analysis mode while we undo
11839          * all the moves. Otherwise we get analysis output after each undo.
11840          */ 
11841         if (first.analysisSupport) {
11842           SendToProgram("exit\nforce\n", &first);
11843           first.analyzing = FALSE;
11844         }
11845     }
11846
11847     if (gameMode == IcsExamining && !pausing) {
11848         SendToICS(ics_prefix);
11849         SendToICS("backward 999999\n");
11850     } else {
11851         BackwardInner(backwardMostMove);
11852     }
11853
11854     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11855         /* we have fed all the moves, so reactivate analysis mode */
11856         SendToProgram("analyze\n", &first);
11857         first.analyzing = TRUE;
11858         /*first.maybeThinking = TRUE;*/
11859         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11860     }
11861 }
11862
11863 void
11864 ToNrEvent(int to)
11865 {
11866   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11867   if (to >= forwardMostMove) to = forwardMostMove;
11868   if (to <= backwardMostMove) to = backwardMostMove;
11869   if (to < currentMove) {
11870     BackwardInner(to);
11871   } else {
11872     ForwardInner(to);
11873   }
11874 }
11875
11876 void
11877 RevertEvent()
11878 {
11879     if (gameMode != IcsExamining) {
11880         DisplayError(_("You are not examining a game"), 0);
11881         return;
11882     }
11883     if (pausing) {
11884         DisplayError(_("You can't revert while pausing"), 0);
11885         return;
11886     }
11887     SendToICS(ics_prefix);
11888     SendToICS("revert\n");
11889 }
11890
11891 void
11892 RetractMoveEvent()
11893 {
11894     switch (gameMode) {
11895       case MachinePlaysWhite:
11896       case MachinePlaysBlack:
11897         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11898             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11899             return;
11900         }
11901         if (forwardMostMove < 2) return;
11902         currentMove = forwardMostMove = forwardMostMove - 2;
11903         whiteTimeRemaining = timeRemaining[0][currentMove];
11904         blackTimeRemaining = timeRemaining[1][currentMove];
11905         DisplayBothClocks();
11906         DisplayMove(currentMove - 1);
11907         ClearHighlights();/*!! could figure this out*/
11908         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11909         SendToProgram("remove\n", &first);
11910         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11911         break;
11912
11913       case BeginningOfGame:
11914       default:
11915         break;
11916
11917       case IcsPlayingWhite:
11918       case IcsPlayingBlack:
11919         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11920             SendToICS(ics_prefix);
11921             SendToICS("takeback 2\n");
11922         } else {
11923             SendToICS(ics_prefix);
11924             SendToICS("takeback 1\n");
11925         }
11926         break;
11927     }
11928 }
11929
11930 void
11931 MoveNowEvent()
11932 {
11933     ChessProgramState *cps;
11934
11935     switch (gameMode) {
11936       case MachinePlaysWhite:
11937         if (!WhiteOnMove(forwardMostMove)) {
11938             DisplayError(_("It is your turn"), 0);
11939             return;
11940         }
11941         cps = &first;
11942         break;
11943       case MachinePlaysBlack:
11944         if (WhiteOnMove(forwardMostMove)) {
11945             DisplayError(_("It is your turn"), 0);
11946             return;
11947         }
11948         cps = &first;
11949         break;
11950       case TwoMachinesPlay:
11951         if (WhiteOnMove(forwardMostMove) ==
11952             (first.twoMachinesColor[0] == 'w')) {
11953             cps = &first;
11954         } else {
11955             cps = &second;
11956         }
11957         break;
11958       case BeginningOfGame:
11959       default:
11960         return;
11961     }
11962     SendToProgram("?\n", cps);
11963 }
11964
11965 void
11966 TruncateGameEvent()
11967 {
11968     EditGameEvent();
11969     if (gameMode != EditGame) return;
11970     TruncateGame();
11971 }
11972
11973 void
11974 TruncateGame()
11975 {
11976     if (forwardMostMove > currentMove) {
11977         if (gameInfo.resultDetails != NULL) {
11978             free(gameInfo.resultDetails);
11979             gameInfo.resultDetails = NULL;
11980             gameInfo.result = GameUnfinished;
11981         }
11982         forwardMostMove = currentMove;
11983         HistorySet(parseList, backwardMostMove, forwardMostMove,
11984                    currentMove-1);
11985     }
11986 }
11987
11988 void
11989 HintEvent()
11990 {
11991     if (appData.noChessProgram) return;
11992     switch (gameMode) {
11993       case MachinePlaysWhite:
11994         if (WhiteOnMove(forwardMostMove)) {
11995             DisplayError(_("Wait until your turn"), 0);
11996             return;
11997         }
11998         break;
11999       case BeginningOfGame:
12000       case MachinePlaysBlack:
12001         if (!WhiteOnMove(forwardMostMove)) {
12002             DisplayError(_("Wait until your turn"), 0);
12003             return;
12004         }
12005         break;
12006       default:
12007         DisplayError(_("No hint available"), 0);
12008         return;
12009     }
12010     SendToProgram("hint\n", &first);
12011     hintRequested = TRUE;
12012 }
12013
12014 void
12015 BookEvent()
12016 {
12017     if (appData.noChessProgram) return;
12018     switch (gameMode) {
12019       case MachinePlaysWhite:
12020         if (WhiteOnMove(forwardMostMove)) {
12021             DisplayError(_("Wait until your turn"), 0);
12022             return;
12023         }
12024         break;
12025       case BeginningOfGame:
12026       case MachinePlaysBlack:
12027         if (!WhiteOnMove(forwardMostMove)) {
12028             DisplayError(_("Wait until your turn"), 0);
12029             return;
12030         }
12031         break;
12032       case EditPosition:
12033         EditPositionDone(TRUE);
12034         break;
12035       case TwoMachinesPlay:
12036         return;
12037       default:
12038         break;
12039     }
12040     SendToProgram("bk\n", &first);
12041     bookOutput[0] = NULLCHAR;
12042     bookRequested = TRUE;
12043 }
12044
12045 void
12046 AboutGameEvent()
12047 {
12048     char *tags = PGNTags(&gameInfo);
12049     TagsPopUp(tags, CmailMsg());
12050     free(tags);
12051 }
12052
12053 /* end button procedures */
12054
12055 void
12056 PrintPosition(fp, move)
12057      FILE *fp;
12058      int move;
12059 {
12060     int i, j;
12061     
12062     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12063         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12064             char c = PieceToChar(boards[move][i][j]);
12065             fputc(c == 'x' ? '.' : c, fp);
12066             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12067         }
12068     }
12069     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12070       fprintf(fp, "white to play\n");
12071     else
12072       fprintf(fp, "black to play\n");
12073 }
12074
12075 void
12076 PrintOpponents(fp)
12077      FILE *fp;
12078 {
12079     if (gameInfo.white != NULL) {
12080         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12081     } else {
12082         fprintf(fp, "\n");
12083     }
12084 }
12085
12086 /* Find last component of program's own name, using some heuristics */
12087 void
12088 TidyProgramName(prog, host, buf)
12089      char *prog, *host, buf[MSG_SIZ];
12090 {
12091     char *p, *q;
12092     int local = (strcmp(host, "localhost") == 0);
12093     while (!local && (p = strchr(prog, ';')) != NULL) {
12094         p++;
12095         while (*p == ' ') p++;
12096         prog = p;
12097     }
12098     if (*prog == '"' || *prog == '\'') {
12099         q = strchr(prog + 1, *prog);
12100     } else {
12101         q = strchr(prog, ' ');
12102     }
12103     if (q == NULL) q = prog + strlen(prog);
12104     p = q;
12105     while (p >= prog && *p != '/' && *p != '\\') p--;
12106     p++;
12107     if(p == prog && *p == '"') p++;
12108     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12109     memcpy(buf, p, q - p);
12110     buf[q - p] = NULLCHAR;
12111     if (!local) {
12112         strcat(buf, "@");
12113         strcat(buf, host);
12114     }
12115 }
12116
12117 char *
12118 TimeControlTagValue()
12119 {
12120     char buf[MSG_SIZ];
12121     if (!appData.clockMode) {
12122         strcpy(buf, "-");
12123     } else if (movesPerSession > 0) {
12124         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12125     } else if (timeIncrement == 0) {
12126         sprintf(buf, "%ld", timeControl/1000);
12127     } else {
12128         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12129     }
12130     return StrSave(buf);
12131 }
12132
12133 void
12134 SetGameInfo()
12135 {
12136     /* This routine is used only for certain modes */
12137     VariantClass v = gameInfo.variant;
12138     ClearGameInfo(&gameInfo);
12139     gameInfo.variant = v;
12140
12141     switch (gameMode) {
12142       case MachinePlaysWhite:
12143         gameInfo.event = StrSave( appData.pgnEventHeader );
12144         gameInfo.site = StrSave(HostName());
12145         gameInfo.date = PGNDate();
12146         gameInfo.round = StrSave("-");
12147         gameInfo.white = StrSave(first.tidy);
12148         gameInfo.black = StrSave(UserName());
12149         gameInfo.timeControl = TimeControlTagValue();
12150         break;
12151
12152       case MachinePlaysBlack:
12153         gameInfo.event = StrSave( appData.pgnEventHeader );
12154         gameInfo.site = StrSave(HostName());
12155         gameInfo.date = PGNDate();
12156         gameInfo.round = StrSave("-");
12157         gameInfo.white = StrSave(UserName());
12158         gameInfo.black = StrSave(first.tidy);
12159         gameInfo.timeControl = TimeControlTagValue();
12160         break;
12161
12162       case TwoMachinesPlay:
12163         gameInfo.event = StrSave( appData.pgnEventHeader );
12164         gameInfo.site = StrSave(HostName());
12165         gameInfo.date = PGNDate();
12166         if (matchGame > 0) {
12167             char buf[MSG_SIZ];
12168             sprintf(buf, "%d", matchGame);
12169             gameInfo.round = StrSave(buf);
12170         } else {
12171             gameInfo.round = StrSave("-");
12172         }
12173         if (first.twoMachinesColor[0] == 'w') {
12174             gameInfo.white = StrSave(first.tidy);
12175             gameInfo.black = StrSave(second.tidy);
12176         } else {
12177             gameInfo.white = StrSave(second.tidy);
12178             gameInfo.black = StrSave(first.tidy);
12179         }
12180         gameInfo.timeControl = TimeControlTagValue();
12181         break;
12182
12183       case EditGame:
12184         gameInfo.event = StrSave("Edited game");
12185         gameInfo.site = StrSave(HostName());
12186         gameInfo.date = PGNDate();
12187         gameInfo.round = StrSave("-");
12188         gameInfo.white = StrSave("-");
12189         gameInfo.black = StrSave("-");
12190         break;
12191
12192       case EditPosition:
12193         gameInfo.event = StrSave("Edited position");
12194         gameInfo.site = StrSave(HostName());
12195         gameInfo.date = PGNDate();
12196         gameInfo.round = StrSave("-");
12197         gameInfo.white = StrSave("-");
12198         gameInfo.black = StrSave("-");
12199         break;
12200
12201       case IcsPlayingWhite:
12202       case IcsPlayingBlack:
12203       case IcsObserving:
12204       case IcsExamining:
12205         break;
12206
12207       case PlayFromGameFile:
12208         gameInfo.event = StrSave("Game from non-PGN file");
12209         gameInfo.site = StrSave(HostName());
12210         gameInfo.date = PGNDate();
12211         gameInfo.round = StrSave("-");
12212         gameInfo.white = StrSave("?");
12213         gameInfo.black = StrSave("?");
12214         break;
12215
12216       default:
12217         break;
12218     }
12219 }
12220
12221 void
12222 ReplaceComment(index, text)
12223      int index;
12224      char *text;
12225 {
12226     int len;
12227
12228     while (*text == '\n') text++;
12229     len = strlen(text);
12230     while (len > 0 && text[len - 1] == '\n') len--;
12231
12232     if (commentList[index] != NULL)
12233       free(commentList[index]);
12234
12235     if (len == 0) {
12236         commentList[index] = NULL;
12237         return;
12238     }
12239     commentList[index] = (char *) malloc(len + 2);
12240     strncpy(commentList[index], text, len);
12241     commentList[index][len] = '\n';
12242     commentList[index][len + 1] = NULLCHAR;
12243 }
12244
12245 void
12246 CrushCRs(text)
12247      char *text;
12248 {
12249   char *p = text;
12250   char *q = text;
12251   char ch;
12252
12253   do {
12254     ch = *p++;
12255     if (ch == '\r') continue;
12256     *q++ = ch;
12257   } while (ch != '\0');
12258 }
12259
12260 void
12261 AppendComment(index, text)
12262      int index;
12263      char *text;
12264 {
12265     int oldlen, len;
12266     char *old;
12267
12268     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12269
12270     CrushCRs(text);
12271     while (*text == '\n') text++;
12272     len = strlen(text);
12273     while (len > 0 && text[len - 1] == '\n') len--;
12274
12275     if (len == 0) return;
12276
12277     if (commentList[index] != NULL) {
12278         old = commentList[index];
12279         oldlen = strlen(old);
12280         commentList[index] = (char *) malloc(oldlen + len + 2);
12281         strcpy(commentList[index], old);
12282         free(old);
12283         strncpy(&commentList[index][oldlen], text, len);
12284         commentList[index][oldlen + len] = '\n';
12285         commentList[index][oldlen + len + 1] = NULLCHAR;
12286     } else {
12287         commentList[index] = (char *) malloc(len + 2);
12288         strncpy(commentList[index], text, len);
12289         commentList[index][len] = '\n';
12290         commentList[index][len + 1] = NULLCHAR;
12291     }
12292 }
12293
12294 static char * FindStr( char * text, char * sub_text )
12295 {
12296     char * result = strstr( text, sub_text );
12297
12298     if( result != NULL ) {
12299         result += strlen( sub_text );
12300     }
12301
12302     return result;
12303 }
12304
12305 /* [AS] Try to extract PV info from PGN comment */
12306 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12307 char *GetInfoFromComment( int index, char * text )
12308 {
12309     char * sep = text;
12310
12311     if( text != NULL && index > 0 ) {
12312         int score = 0;
12313         int depth = 0;
12314         int time = -1, sec = 0, deci;
12315         char * s_eval = FindStr( text, "[%eval " );
12316         char * s_emt = FindStr( text, "[%emt " );
12317
12318         if( s_eval != NULL || s_emt != NULL ) {
12319             /* New style */
12320             char delim;
12321
12322             if( s_eval != NULL ) {
12323                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12324                     return text;
12325                 }
12326
12327                 if( delim != ']' ) {
12328                     return text;
12329                 }
12330             }
12331
12332             if( s_emt != NULL ) {
12333             }
12334         }
12335         else {
12336             /* We expect something like: [+|-]nnn.nn/dd */
12337             int score_lo = 0;
12338
12339             sep = strchr( text, '/' );
12340             if( sep == NULL || sep < (text+4) ) {
12341                 return text;
12342             }
12343
12344             time = -1; sec = -1; deci = -1;
12345             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12346                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12347                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12348                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12349                 return text;
12350             }
12351
12352             if( score_lo < 0 || score_lo >= 100 ) {
12353                 return text;
12354             }
12355
12356             if(sec >= 0) time = 600*time + 10*sec; else
12357             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12358
12359             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12360
12361             /* [HGM] PV time: now locate end of PV info */
12362             while( *++sep >= '0' && *sep <= '9'); // strip depth
12363             if(time >= 0)
12364             while( *++sep >= '0' && *sep <= '9'); // strip time
12365             if(sec >= 0)
12366             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12367             if(deci >= 0)
12368             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12369             while(*sep == ' ') sep++;
12370         }
12371
12372         if( depth <= 0 ) {
12373             return text;
12374         }
12375
12376         if( time < 0 ) {
12377             time = -1;
12378         }
12379
12380         pvInfoList[index-1].depth = depth;
12381         pvInfoList[index-1].score = score;
12382         pvInfoList[index-1].time  = 10*time; // centi-sec
12383     }
12384     return sep;
12385 }
12386
12387 void
12388 SendToProgram(message, cps)
12389      char *message;
12390      ChessProgramState *cps;
12391 {
12392     int count, outCount, error;
12393     char buf[MSG_SIZ];
12394
12395     if (cps->pr == NULL) return;
12396     Attention(cps);
12397     
12398     if (appData.debugMode) {
12399         TimeMark now;
12400         GetTimeMark(&now);
12401         fprintf(debugFP, "%ld >%-6s: %s", 
12402                 SubtractTimeMarks(&now, &programStartTime),
12403                 cps->which, message);
12404     }
12405     
12406     count = strlen(message);
12407     outCount = OutputToProcess(cps->pr, message, count, &error);
12408     if (outCount < count && !exiting 
12409                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12410         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12411         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12412             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12413                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12414                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12415             } else {
12416                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12417             }
12418             gameInfo.resultDetails = StrSave(buf);
12419         }
12420         DisplayFatalError(buf, error, 1);
12421     }
12422 }
12423
12424 void
12425 ReceiveFromProgram(isr, closure, message, count, error)
12426      InputSourceRef isr;
12427      VOIDSTAR closure;
12428      char *message;
12429      int count;
12430      int error;
12431 {
12432     char *end_str;
12433     char buf[MSG_SIZ];
12434     ChessProgramState *cps = (ChessProgramState *)closure;
12435
12436     if (isr != cps->isr) return; /* Killed intentionally */
12437     if (count <= 0) {
12438         if (count == 0) {
12439             sprintf(buf,
12440                     _("Error: %s chess program (%s) exited unexpectedly"),
12441                     cps->which, cps->program);
12442         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12443                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12444                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12445                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12446                 } else {
12447                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12448                 }
12449                 gameInfo.resultDetails = StrSave(buf);
12450             }
12451             RemoveInputSource(cps->isr);
12452             DisplayFatalError(buf, 0, 1);
12453         } else {
12454             sprintf(buf,
12455                     _("Error reading from %s chess program (%s)"),
12456                     cps->which, cps->program);
12457             RemoveInputSource(cps->isr);
12458
12459             /* [AS] Program is misbehaving badly... kill it */
12460             if( count == -2 ) {
12461                 DestroyChildProcess( cps->pr, 9 );
12462                 cps->pr = NoProc;
12463             }
12464
12465             DisplayFatalError(buf, error, 1);
12466         }
12467         return;
12468     }
12469     
12470     if ((end_str = strchr(message, '\r')) != NULL)
12471       *end_str = NULLCHAR;
12472     if ((end_str = strchr(message, '\n')) != NULL)
12473       *end_str = NULLCHAR;
12474     
12475     if (appData.debugMode) {
12476         TimeMark now; int print = 1;
12477         char *quote = ""; char c; int i;
12478
12479         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12480                 char start = message[0];
12481                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12482                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12483                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12484                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12485                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12486                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12487                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12488                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12489                         { quote = "# "; print = (appData.engineComments == 2); }
12490                 message[0] = start; // restore original message
12491         }
12492         if(print) {
12493                 GetTimeMark(&now);
12494                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12495                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12496                         quote,
12497                         message);
12498         }
12499     }
12500
12501     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12502     if (appData.icsEngineAnalyze) {
12503         if (strstr(message, "whisper") != NULL ||
12504              strstr(message, "kibitz") != NULL || 
12505             strstr(message, "tellics") != NULL) return;
12506     }
12507
12508     HandleMachineMove(message, cps);
12509 }
12510
12511
12512 void
12513 SendTimeControl(cps, mps, tc, inc, sd, st)
12514      ChessProgramState *cps;
12515      int mps, inc, sd, st;
12516      long tc;
12517 {
12518     char buf[MSG_SIZ];
12519     int seconds;
12520
12521     if( timeControl_2 > 0 ) {
12522         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12523             tc = timeControl_2;
12524         }
12525     }
12526     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12527     inc /= cps->timeOdds;
12528     st  /= cps->timeOdds;
12529
12530     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12531
12532     if (st > 0) {
12533       /* Set exact time per move, normally using st command */
12534       if (cps->stKludge) {
12535         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12536         seconds = st % 60;
12537         if (seconds == 0) {
12538           sprintf(buf, "level 1 %d\n", st/60);
12539         } else {
12540           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12541         }
12542       } else {
12543         sprintf(buf, "st %d\n", st);
12544       }
12545     } else {
12546       /* Set conventional or incremental time control, using level command */
12547       if (seconds == 0) {
12548         /* Note old gnuchess bug -- minutes:seconds used to not work.
12549            Fixed in later versions, but still avoid :seconds
12550            when seconds is 0. */
12551         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12552       } else {
12553         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12554                 seconds, inc/1000);
12555       }
12556     }
12557     SendToProgram(buf, cps);
12558
12559     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12560     /* Orthogonally, limit search to given depth */
12561     if (sd > 0) {
12562       if (cps->sdKludge) {
12563         sprintf(buf, "depth\n%d\n", sd);
12564       } else {
12565         sprintf(buf, "sd %d\n", sd);
12566       }
12567       SendToProgram(buf, cps);
12568     }
12569
12570     if(cps->nps > 0) { /* [HGM] nps */
12571         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12572         else {
12573                 sprintf(buf, "nps %d\n", cps->nps);
12574               SendToProgram(buf, cps);
12575         }
12576     }
12577 }
12578
12579 ChessProgramState *WhitePlayer()
12580 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12581 {
12582     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12583        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12584         return &second;
12585     return &first;
12586 }
12587
12588 void
12589 SendTimeRemaining(cps, machineWhite)
12590      ChessProgramState *cps;
12591      int /*boolean*/ machineWhite;
12592 {
12593     char message[MSG_SIZ];
12594     long time, otime;
12595
12596     /* Note: this routine must be called when the clocks are stopped
12597        or when they have *just* been set or switched; otherwise
12598        it will be off by the time since the current tick started.
12599     */
12600     if (machineWhite) {
12601         time = whiteTimeRemaining / 10;
12602         otime = blackTimeRemaining / 10;
12603     } else {
12604         time = blackTimeRemaining / 10;
12605         otime = whiteTimeRemaining / 10;
12606     }
12607     /* [HGM] translate opponent's time by time-odds factor */
12608     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12609     if (appData.debugMode) {
12610         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12611     }
12612
12613     if (time <= 0) time = 1;
12614     if (otime <= 0) otime = 1;
12615     
12616     sprintf(message, "time %ld\n", time);
12617     SendToProgram(message, cps);
12618
12619     sprintf(message, "otim %ld\n", otime);
12620     SendToProgram(message, cps);
12621 }
12622
12623 int
12624 BoolFeature(p, name, loc, cps)
12625      char **p;
12626      char *name;
12627      int *loc;
12628      ChessProgramState *cps;
12629 {
12630   char buf[MSG_SIZ];
12631   int len = strlen(name);
12632   int val;
12633   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12634     (*p) += len + 1;
12635     sscanf(*p, "%d", &val);
12636     *loc = (val != 0);
12637     while (**p && **p != ' ') (*p)++;
12638     sprintf(buf, "accepted %s\n", name);
12639     SendToProgram(buf, cps);
12640     return TRUE;
12641   }
12642   return FALSE;
12643 }
12644
12645 int
12646 IntFeature(p, name, loc, cps)
12647      char **p;
12648      char *name;
12649      int *loc;
12650      ChessProgramState *cps;
12651 {
12652   char buf[MSG_SIZ];
12653   int len = strlen(name);
12654   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12655     (*p) += len + 1;
12656     sscanf(*p, "%d", loc);
12657     while (**p && **p != ' ') (*p)++;
12658     sprintf(buf, "accepted %s\n", name);
12659     SendToProgram(buf, cps);
12660     return TRUE;
12661   }
12662   return FALSE;
12663 }
12664
12665 int
12666 StringFeature(p, name, loc, cps)
12667      char **p;
12668      char *name;
12669      char loc[];
12670      ChessProgramState *cps;
12671 {
12672   char buf[MSG_SIZ];
12673   int len = strlen(name);
12674   if (strncmp((*p), name, len) == 0
12675       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12676     (*p) += len + 2;
12677     sscanf(*p, "%[^\"]", loc);
12678     while (**p && **p != '\"') (*p)++;
12679     if (**p == '\"') (*p)++;
12680     sprintf(buf, "accepted %s\n", name);
12681     SendToProgram(buf, cps);
12682     return TRUE;
12683   }
12684   return FALSE;
12685 }
12686
12687 int 
12688 ParseOption(Option *opt, ChessProgramState *cps)
12689 // [HGM] options: process the string that defines an engine option, and determine
12690 // name, type, default value, and allowed value range
12691 {
12692         char *p, *q, buf[MSG_SIZ];
12693         int n, min = (-1)<<31, max = 1<<31, def;
12694
12695         if(p = strstr(opt->name, " -spin ")) {
12696             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12697             if(max < min) max = min; // enforce consistency
12698             if(def < min) def = min;
12699             if(def > max) def = max;
12700             opt->value = def;
12701             opt->min = min;
12702             opt->max = max;
12703             opt->type = Spin;
12704         } else if((p = strstr(opt->name, " -slider "))) {
12705             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12706             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12707             if(max < min) max = min; // enforce consistency
12708             if(def < min) def = min;
12709             if(def > max) def = max;
12710             opt->value = def;
12711             opt->min = min;
12712             opt->max = max;
12713             opt->type = Spin; // Slider;
12714         } else if((p = strstr(opt->name, " -string "))) {
12715             opt->textValue = p+9;
12716             opt->type = TextBox;
12717         } else if((p = strstr(opt->name, " -file "))) {
12718             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12719             opt->textValue = p+7;
12720             opt->type = TextBox; // FileName;
12721         } else if((p = strstr(opt->name, " -path "))) {
12722             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12723             opt->textValue = p+7;
12724             opt->type = TextBox; // PathName;
12725         } else if(p = strstr(opt->name, " -check ")) {
12726             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12727             opt->value = (def != 0);
12728             opt->type = CheckBox;
12729         } else if(p = strstr(opt->name, " -combo ")) {
12730             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12731             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12732             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12733             opt->value = n = 0;
12734             while(q = StrStr(q, " /// ")) {
12735                 n++; *q = 0;    // count choices, and null-terminate each of them
12736                 q += 5;
12737                 if(*q == '*') { // remember default, which is marked with * prefix
12738                     q++;
12739                     opt->value = n;
12740                 }
12741                 cps->comboList[cps->comboCnt++] = q;
12742             }
12743             cps->comboList[cps->comboCnt++] = NULL;
12744             opt->max = n + 1;
12745             opt->type = ComboBox;
12746         } else if(p = strstr(opt->name, " -button")) {
12747             opt->type = Button;
12748         } else if(p = strstr(opt->name, " -save")) {
12749             opt->type = SaveButton;
12750         } else return FALSE;
12751         *p = 0; // terminate option name
12752         // now look if the command-line options define a setting for this engine option.
12753         if(cps->optionSettings && cps->optionSettings[0])
12754             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12755         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12756                 sprintf(buf, "option %s", p);
12757                 if(p = strstr(buf, ",")) *p = 0;
12758                 strcat(buf, "\n");
12759                 SendToProgram(buf, cps);
12760         }
12761         return TRUE;
12762 }
12763
12764 void
12765 FeatureDone(cps, val)
12766      ChessProgramState* cps;
12767      int val;
12768 {
12769   DelayedEventCallback cb = GetDelayedEvent();
12770   if ((cb == InitBackEnd3 && cps == &first) ||
12771       (cb == TwoMachinesEventIfReady && cps == &second)) {
12772     CancelDelayedEvent();
12773     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12774   }
12775   cps->initDone = val;
12776 }
12777
12778 /* Parse feature command from engine */
12779 void
12780 ParseFeatures(args, cps)
12781      char* args;
12782      ChessProgramState *cps;  
12783 {
12784   char *p = args;
12785   char *q;
12786   int val;
12787   char buf[MSG_SIZ];
12788
12789   for (;;) {
12790     while (*p == ' ') p++;
12791     if (*p == NULLCHAR) return;
12792
12793     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12794     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12795     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12796     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12797     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12798     if (BoolFeature(&p, "reuse", &val, cps)) {
12799       /* Engine can disable reuse, but can't enable it if user said no */
12800       if (!val) cps->reuse = FALSE;
12801       continue;
12802     }
12803     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12804     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12805       if (gameMode == TwoMachinesPlay) {
12806         DisplayTwoMachinesTitle();
12807       } else {
12808         DisplayTitle("");
12809       }
12810       continue;
12811     }
12812     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12813     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12814     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12815     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12816     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12817     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12818     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12819     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12820     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12821     if (IntFeature(&p, "done", &val, cps)) {
12822       FeatureDone(cps, val);
12823       continue;
12824     }
12825     /* Added by Tord: */
12826     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12827     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12828     /* End of additions by Tord */
12829
12830     /* [HGM] added features: */
12831     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12832     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12833     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12834     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12835     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12836     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12837     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12838         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12839             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12840             SendToProgram(buf, cps);
12841             continue;
12842         }
12843         if(cps->nrOptions >= MAX_OPTIONS) {
12844             cps->nrOptions--;
12845             sprintf(buf, "%s engine has too many options\n", cps->which);
12846             DisplayError(buf, 0);
12847         }
12848         continue;
12849     }
12850     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12851     /* End of additions by HGM */
12852
12853     /* unknown feature: complain and skip */
12854     q = p;
12855     while (*q && *q != '=') q++;
12856     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12857     SendToProgram(buf, cps);
12858     p = q;
12859     if (*p == '=') {
12860       p++;
12861       if (*p == '\"') {
12862         p++;
12863         while (*p && *p != '\"') p++;
12864         if (*p == '\"') p++;
12865       } else {
12866         while (*p && *p != ' ') p++;
12867       }
12868     }
12869   }
12870
12871 }
12872
12873 void
12874 PeriodicUpdatesEvent(newState)
12875      int newState;
12876 {
12877     if (newState == appData.periodicUpdates)
12878       return;
12879
12880     appData.periodicUpdates=newState;
12881
12882     /* Display type changes, so update it now */
12883 //    DisplayAnalysis();
12884
12885     /* Get the ball rolling again... */
12886     if (newState) {
12887         AnalysisPeriodicEvent(1);
12888         StartAnalysisClock();
12889     }
12890 }
12891
12892 void
12893 PonderNextMoveEvent(newState)
12894      int newState;
12895 {
12896     if (newState == appData.ponderNextMove) return;
12897     if (gameMode == EditPosition) EditPositionDone(TRUE);
12898     if (newState) {
12899         SendToProgram("hard\n", &first);
12900         if (gameMode == TwoMachinesPlay) {
12901             SendToProgram("hard\n", &second);
12902         }
12903     } else {
12904         SendToProgram("easy\n", &first);
12905         thinkOutput[0] = NULLCHAR;
12906         if (gameMode == TwoMachinesPlay) {
12907             SendToProgram("easy\n", &second);
12908         }
12909     }
12910     appData.ponderNextMove = newState;
12911 }
12912
12913 void
12914 NewSettingEvent(option, command, value)
12915      char *command;
12916      int option, value;
12917 {
12918     char buf[MSG_SIZ];
12919
12920     if (gameMode == EditPosition) EditPositionDone(TRUE);
12921     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12922     SendToProgram(buf, &first);
12923     if (gameMode == TwoMachinesPlay) {
12924         SendToProgram(buf, &second);
12925     }
12926 }
12927
12928 void
12929 ShowThinkingEvent()
12930 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12931 {
12932     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12933     int newState = appData.showThinking
12934         // [HGM] thinking: other features now need thinking output as well
12935         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12936     
12937     if (oldState == newState) return;
12938     oldState = newState;
12939     if (gameMode == EditPosition) EditPositionDone(TRUE);
12940     if (oldState) {
12941         SendToProgram("post\n", &first);
12942         if (gameMode == TwoMachinesPlay) {
12943             SendToProgram("post\n", &second);
12944         }
12945     } else {
12946         SendToProgram("nopost\n", &first);
12947         thinkOutput[0] = NULLCHAR;
12948         if (gameMode == TwoMachinesPlay) {
12949             SendToProgram("nopost\n", &second);
12950         }
12951     }
12952 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12953 }
12954
12955 void
12956 AskQuestionEvent(title, question, replyPrefix, which)
12957      char *title; char *question; char *replyPrefix; char *which;
12958 {
12959   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12960   if (pr == NoProc) return;
12961   AskQuestion(title, question, replyPrefix, pr);
12962 }
12963
12964 void
12965 DisplayMove(moveNumber)
12966      int moveNumber;
12967 {
12968     char message[MSG_SIZ];
12969     char res[MSG_SIZ];
12970     char cpThinkOutput[MSG_SIZ];
12971
12972     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12973     
12974     if (moveNumber == forwardMostMove - 1 || 
12975         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12976
12977         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12978
12979         if (strchr(cpThinkOutput, '\n')) {
12980             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12981         }
12982     } else {
12983         *cpThinkOutput = NULLCHAR;
12984     }
12985
12986     /* [AS] Hide thinking from human user */
12987     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12988         *cpThinkOutput = NULLCHAR;
12989         if( thinkOutput[0] != NULLCHAR ) {
12990             int i;
12991
12992             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12993                 cpThinkOutput[i] = '.';
12994             }
12995             cpThinkOutput[i] = NULLCHAR;
12996             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12997         }
12998     }
12999
13000     if (moveNumber == forwardMostMove - 1 &&
13001         gameInfo.resultDetails != NULL) {
13002         if (gameInfo.resultDetails[0] == NULLCHAR) {
13003             sprintf(res, " %s", PGNResult(gameInfo.result));
13004         } else {
13005             sprintf(res, " {%s} %s",
13006                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13007         }
13008     } else {
13009         res[0] = NULLCHAR;
13010     }
13011
13012     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13013         DisplayMessage(res, cpThinkOutput);
13014     } else {
13015         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13016                 WhiteOnMove(moveNumber) ? " " : ".. ",
13017                 parseList[moveNumber], res);
13018         DisplayMessage(message, cpThinkOutput);
13019     }
13020 }
13021
13022 void
13023 DisplayComment(moveNumber, text)
13024      int moveNumber;
13025      char *text;
13026 {
13027     char title[MSG_SIZ];
13028     char buf[8000]; // comment can be long!
13029     int score, depth;
13030     
13031     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13032       strcpy(title, "Comment");
13033     } else {
13034       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13035               WhiteOnMove(moveNumber) ? " " : ".. ",
13036               parseList[moveNumber]);
13037     }
13038     // [HGM] PV info: display PV info together with (or as) comment
13039     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13040       if(text == NULL) text = "";                                           
13041       score = pvInfoList[moveNumber].score;
13042       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13043               depth, (pvInfoList[moveNumber].time+50)/100, text);
13044       text = buf;
13045     }
13046     if (text != NULL && (appData.autoDisplayComment || commentUp))
13047         CommentPopUp(title, text);
13048 }
13049
13050 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13051  * might be busy thinking or pondering.  It can be omitted if your
13052  * gnuchess is configured to stop thinking immediately on any user
13053  * input.  However, that gnuchess feature depends on the FIONREAD
13054  * ioctl, which does not work properly on some flavors of Unix.
13055  */
13056 void
13057 Attention(cps)
13058      ChessProgramState *cps;
13059 {
13060 #if ATTENTION
13061     if (!cps->useSigint) return;
13062     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13063     switch (gameMode) {
13064       case MachinePlaysWhite:
13065       case MachinePlaysBlack:
13066       case TwoMachinesPlay:
13067       case IcsPlayingWhite:
13068       case IcsPlayingBlack:
13069       case AnalyzeMode:
13070       case AnalyzeFile:
13071         /* Skip if we know it isn't thinking */
13072         if (!cps->maybeThinking) return;
13073         if (appData.debugMode)
13074           fprintf(debugFP, "Interrupting %s\n", cps->which);
13075         InterruptChildProcess(cps->pr);
13076         cps->maybeThinking = FALSE;
13077         break;
13078       default:
13079         break;
13080     }
13081 #endif /*ATTENTION*/
13082 }
13083
13084 int
13085 CheckFlags()
13086 {
13087     if (whiteTimeRemaining <= 0) {
13088         if (!whiteFlag) {
13089             whiteFlag = TRUE;
13090             if (appData.icsActive) {
13091                 if (appData.autoCallFlag &&
13092                     gameMode == IcsPlayingBlack && !blackFlag) {
13093                   SendToICS(ics_prefix);
13094                   SendToICS("flag\n");
13095                 }
13096             } else {
13097                 if (blackFlag) {
13098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13099                 } else {
13100                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13101                     if (appData.autoCallFlag) {
13102                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13103                         return TRUE;
13104                     }
13105                 }
13106             }
13107         }
13108     }
13109     if (blackTimeRemaining <= 0) {
13110         if (!blackFlag) {
13111             blackFlag = TRUE;
13112             if (appData.icsActive) {
13113                 if (appData.autoCallFlag &&
13114                     gameMode == IcsPlayingWhite && !whiteFlag) {
13115                   SendToICS(ics_prefix);
13116                   SendToICS("flag\n");
13117                 }
13118             } else {
13119                 if (whiteFlag) {
13120                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13121                 } else {
13122                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13123                     if (appData.autoCallFlag) {
13124                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13125                         return TRUE;
13126                     }
13127                 }
13128             }
13129         }
13130     }
13131     return FALSE;
13132 }
13133
13134 void
13135 CheckTimeControl()
13136 {
13137     if (!appData.clockMode || appData.icsActive ||
13138         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13139
13140     /*
13141      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13142      */
13143     if ( !WhiteOnMove(forwardMostMove) )
13144         /* White made time control */
13145         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13146         /* [HGM] time odds: correct new time quota for time odds! */
13147                                             / WhitePlayer()->timeOdds;
13148       else
13149         /* Black made time control */
13150         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13151                                             / WhitePlayer()->other->timeOdds;
13152 }
13153
13154 void
13155 DisplayBothClocks()
13156 {
13157     int wom = gameMode == EditPosition ?
13158       !blackPlaysFirst : WhiteOnMove(currentMove);
13159     DisplayWhiteClock(whiteTimeRemaining, wom);
13160     DisplayBlackClock(blackTimeRemaining, !wom);
13161 }
13162
13163
13164 /* Timekeeping seems to be a portability nightmare.  I think everyone
13165    has ftime(), but I'm really not sure, so I'm including some ifdefs
13166    to use other calls if you don't.  Clocks will be less accurate if
13167    you have neither ftime nor gettimeofday.
13168 */
13169
13170 /* VS 2008 requires the #include outside of the function */
13171 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13172 #include <sys/timeb.h>
13173 #endif
13174
13175 /* Get the current time as a TimeMark */
13176 void
13177 GetTimeMark(tm)
13178      TimeMark *tm;
13179 {
13180 #if HAVE_GETTIMEOFDAY
13181
13182     struct timeval timeVal;
13183     struct timezone timeZone;
13184
13185     gettimeofday(&timeVal, &timeZone);
13186     tm->sec = (long) timeVal.tv_sec; 
13187     tm->ms = (int) (timeVal.tv_usec / 1000L);
13188
13189 #else /*!HAVE_GETTIMEOFDAY*/
13190 #if HAVE_FTIME
13191
13192 // include <sys/timeb.h> / moved to just above start of function
13193     struct timeb timeB;
13194
13195     ftime(&timeB);
13196     tm->sec = (long) timeB.time;
13197     tm->ms = (int) timeB.millitm;
13198
13199 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13200     tm->sec = (long) time(NULL);
13201     tm->ms = 0;
13202 #endif
13203 #endif
13204 }
13205
13206 /* Return the difference in milliseconds between two
13207    time marks.  We assume the difference will fit in a long!
13208 */
13209 long
13210 SubtractTimeMarks(tm2, tm1)
13211      TimeMark *tm2, *tm1;
13212 {
13213     return 1000L*(tm2->sec - tm1->sec) +
13214            (long) (tm2->ms - tm1->ms);
13215 }
13216
13217
13218 /*
13219  * Code to manage the game clocks.
13220  *
13221  * In tournament play, black starts the clock and then white makes a move.
13222  * We give the human user a slight advantage if he is playing white---the
13223  * clocks don't run until he makes his first move, so it takes zero time.
13224  * Also, we don't account for network lag, so we could get out of sync
13225  * with GNU Chess's clock -- but then, referees are always right.  
13226  */
13227
13228 static TimeMark tickStartTM;
13229 static long intendedTickLength;
13230
13231 long
13232 NextTickLength(timeRemaining)
13233      long timeRemaining;
13234 {
13235     long nominalTickLength, nextTickLength;
13236
13237     if (timeRemaining > 0L && timeRemaining <= 10000L)
13238       nominalTickLength = 100L;
13239     else
13240       nominalTickLength = 1000L;
13241     nextTickLength = timeRemaining % nominalTickLength;
13242     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13243
13244     return nextTickLength;
13245 }
13246
13247 /* Adjust clock one minute up or down */
13248 void
13249 AdjustClock(Boolean which, int dir)
13250 {
13251     if(which) blackTimeRemaining += 60000*dir;
13252     else      whiteTimeRemaining += 60000*dir;
13253     DisplayBothClocks();
13254 }
13255
13256 /* Stop clocks and reset to a fresh time control */
13257 void
13258 ResetClocks() 
13259 {
13260     (void) StopClockTimer();
13261     if (appData.icsActive) {
13262         whiteTimeRemaining = blackTimeRemaining = 0;
13263     } else { /* [HGM] correct new time quote for time odds */
13264         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13265         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13266     }
13267     if (whiteFlag || blackFlag) {
13268         DisplayTitle("");
13269         whiteFlag = blackFlag = FALSE;
13270     }
13271     DisplayBothClocks();
13272 }
13273
13274 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13275
13276 /* Decrement running clock by amount of time that has passed */
13277 void
13278 DecrementClocks()
13279 {
13280     long timeRemaining;
13281     long lastTickLength, fudge;
13282     TimeMark now;
13283
13284     if (!appData.clockMode) return;
13285     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13286         
13287     GetTimeMark(&now);
13288
13289     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13290
13291     /* Fudge if we woke up a little too soon */
13292     fudge = intendedTickLength - lastTickLength;
13293     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13294
13295     if (WhiteOnMove(forwardMostMove)) {
13296         if(whiteNPS >= 0) lastTickLength = 0;
13297         timeRemaining = whiteTimeRemaining -= lastTickLength;
13298         DisplayWhiteClock(whiteTimeRemaining - fudge,
13299                           WhiteOnMove(currentMove));
13300     } else {
13301         if(blackNPS >= 0) lastTickLength = 0;
13302         timeRemaining = blackTimeRemaining -= lastTickLength;
13303         DisplayBlackClock(blackTimeRemaining - fudge,
13304                           !WhiteOnMove(currentMove));
13305     }
13306
13307     if (CheckFlags()) return;
13308         
13309     tickStartTM = now;
13310     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13311     StartClockTimer(intendedTickLength);
13312
13313     /* if the time remaining has fallen below the alarm threshold, sound the
13314      * alarm. if the alarm has sounded and (due to a takeback or time control
13315      * with increment) the time remaining has increased to a level above the
13316      * threshold, reset the alarm so it can sound again. 
13317      */
13318     
13319     if (appData.icsActive && appData.icsAlarm) {
13320
13321         /* make sure we are dealing with the user's clock */
13322         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13323                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13324            )) return;
13325
13326         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13327             alarmSounded = FALSE;
13328         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13329             PlayAlarmSound();
13330             alarmSounded = TRUE;
13331         }
13332     }
13333 }
13334
13335
13336 /* A player has just moved, so stop the previously running
13337    clock and (if in clock mode) start the other one.
13338    We redisplay both clocks in case we're in ICS mode, because
13339    ICS gives us an update to both clocks after every move.
13340    Note that this routine is called *after* forwardMostMove
13341    is updated, so the last fractional tick must be subtracted
13342    from the color that is *not* on move now.
13343 */
13344 void
13345 SwitchClocks()
13346 {
13347     long lastTickLength;
13348     TimeMark now;
13349     int flagged = FALSE;
13350
13351     GetTimeMark(&now);
13352
13353     if (StopClockTimer() && appData.clockMode) {
13354         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13355         if (WhiteOnMove(forwardMostMove)) {
13356             if(blackNPS >= 0) lastTickLength = 0;
13357             blackTimeRemaining -= lastTickLength;
13358            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13359 //         if(pvInfoList[forwardMostMove-1].time == -1)
13360                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13361                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13362         } else {
13363            if(whiteNPS >= 0) lastTickLength = 0;
13364            whiteTimeRemaining -= lastTickLength;
13365            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13366 //         if(pvInfoList[forwardMostMove-1].time == -1)
13367                  pvInfoList[forwardMostMove-1].time = 
13368                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13369         }
13370         flagged = CheckFlags();
13371     }
13372     CheckTimeControl();
13373
13374     if (flagged || !appData.clockMode) return;
13375
13376     switch (gameMode) {
13377       case MachinePlaysBlack:
13378       case MachinePlaysWhite:
13379       case BeginningOfGame:
13380         if (pausing) return;
13381         break;
13382
13383       case EditGame:
13384       case PlayFromGameFile:
13385       case IcsExamining:
13386         return;
13387
13388       default:
13389         break;
13390     }
13391
13392     tickStartTM = now;
13393     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13394       whiteTimeRemaining : blackTimeRemaining);
13395     StartClockTimer(intendedTickLength);
13396 }
13397         
13398
13399 /* Stop both clocks */
13400 void
13401 StopClocks()
13402 {       
13403     long lastTickLength;
13404     TimeMark now;
13405
13406     if (!StopClockTimer()) return;
13407     if (!appData.clockMode) return;
13408
13409     GetTimeMark(&now);
13410
13411     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13412     if (WhiteOnMove(forwardMostMove)) {
13413         if(whiteNPS >= 0) lastTickLength = 0;
13414         whiteTimeRemaining -= lastTickLength;
13415         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13416     } else {
13417         if(blackNPS >= 0) lastTickLength = 0;
13418         blackTimeRemaining -= lastTickLength;
13419         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13420     }
13421     CheckFlags();
13422 }
13423         
13424 /* Start clock of player on move.  Time may have been reset, so
13425    if clock is already running, stop and restart it. */
13426 void
13427 StartClocks()
13428 {
13429     (void) StopClockTimer(); /* in case it was running already */
13430     DisplayBothClocks();
13431     if (CheckFlags()) return;
13432
13433     if (!appData.clockMode) return;
13434     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13435
13436     GetTimeMark(&tickStartTM);
13437     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13438       whiteTimeRemaining : blackTimeRemaining);
13439
13440    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13441     whiteNPS = blackNPS = -1; 
13442     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13443        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13444         whiteNPS = first.nps;
13445     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13446        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13447         blackNPS = first.nps;
13448     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13449         whiteNPS = second.nps;
13450     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13451         blackNPS = second.nps;
13452     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13453
13454     StartClockTimer(intendedTickLength);
13455 }
13456
13457 char *
13458 TimeString(ms)
13459      long ms;
13460 {
13461     long second, minute, hour, day;
13462     char *sign = "";
13463     static char buf[32];
13464     
13465     if (ms > 0 && ms <= 9900) {
13466       /* convert milliseconds to tenths, rounding up */
13467       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13468
13469       sprintf(buf, " %03.1f ", tenths/10.0);
13470       return buf;
13471     }
13472
13473     /* convert milliseconds to seconds, rounding up */
13474     /* use floating point to avoid strangeness of integer division
13475        with negative dividends on many machines */
13476     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13477
13478     if (second < 0) {
13479         sign = "-";
13480         second = -second;
13481     }
13482     
13483     day = second / (60 * 60 * 24);
13484     second = second % (60 * 60 * 24);
13485     hour = second / (60 * 60);
13486     second = second % (60 * 60);
13487     minute = second / 60;
13488     second = second % 60;
13489     
13490     if (day > 0)
13491       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13492               sign, day, hour, minute, second);
13493     else if (hour > 0)
13494       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13495     else
13496       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13497     
13498     return buf;
13499 }
13500
13501
13502 /*
13503  * This is necessary because some C libraries aren't ANSI C compliant yet.
13504  */
13505 char *
13506 StrStr(string, match)
13507      char *string, *match;
13508 {
13509     int i, length;
13510     
13511     length = strlen(match);
13512     
13513     for (i = strlen(string) - length; i >= 0; i--, string++)
13514       if (!strncmp(match, string, length))
13515         return string;
13516     
13517     return NULL;
13518 }
13519
13520 char *
13521 StrCaseStr(string, match)
13522      char *string, *match;
13523 {
13524     int i, j, length;
13525     
13526     length = strlen(match);
13527     
13528     for (i = strlen(string) - length; i >= 0; i--, string++) {
13529         for (j = 0; j < length; j++) {
13530             if (ToLower(match[j]) != ToLower(string[j]))
13531               break;
13532         }
13533         if (j == length) return string;
13534     }
13535
13536     return NULL;
13537 }
13538
13539 #ifndef _amigados
13540 int
13541 StrCaseCmp(s1, s2)
13542      char *s1, *s2;
13543 {
13544     char c1, c2;
13545     
13546     for (;;) {
13547         c1 = ToLower(*s1++);
13548         c2 = ToLower(*s2++);
13549         if (c1 > c2) return 1;
13550         if (c1 < c2) return -1;
13551         if (c1 == NULLCHAR) return 0;
13552     }
13553 }
13554
13555
13556 int
13557 ToLower(c)
13558      int c;
13559 {
13560     return isupper(c) ? tolower(c) : c;
13561 }
13562
13563
13564 int
13565 ToUpper(c)
13566      int c;
13567 {
13568     return islower(c) ? toupper(c) : c;
13569 }
13570 #endif /* !_amigados    */
13571
13572 char *
13573 StrSave(s)
13574      char *s;
13575 {
13576     char *ret;
13577
13578     if ((ret = (char *) malloc(strlen(s) + 1))) {
13579         strcpy(ret, s);
13580     }
13581     return ret;
13582 }
13583
13584 char *
13585 StrSavePtr(s, savePtr)
13586      char *s, **savePtr;
13587 {
13588     if (*savePtr) {
13589         free(*savePtr);
13590     }
13591     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13592         strcpy(*savePtr, s);
13593     }
13594     return(*savePtr);
13595 }
13596
13597 char *
13598 PGNDate()
13599 {
13600     time_t clock;
13601     struct tm *tm;
13602     char buf[MSG_SIZ];
13603
13604     clock = time((time_t *)NULL);
13605     tm = localtime(&clock);
13606     sprintf(buf, "%04d.%02d.%02d",
13607             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13608     return StrSave(buf);
13609 }
13610
13611
13612 char *
13613 PositionToFEN(move, overrideCastling)
13614      int move;
13615      char *overrideCastling;
13616 {
13617     int i, j, fromX, fromY, toX, toY;
13618     int whiteToPlay;
13619     char buf[128];
13620     char *p, *q;
13621     int emptycount;
13622     ChessSquare piece;
13623
13624     whiteToPlay = (gameMode == EditPosition) ?
13625       !blackPlaysFirst : (move % 2 == 0);
13626     p = buf;
13627
13628     /* Piece placement data */
13629     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13630         emptycount = 0;
13631         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13632             if (boards[move][i][j] == EmptySquare) {
13633                 emptycount++;
13634             } else { ChessSquare piece = boards[move][i][j];
13635                 if (emptycount > 0) {
13636                     if(emptycount<10) /* [HGM] can be >= 10 */
13637                         *p++ = '0' + emptycount;
13638                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13639                     emptycount = 0;
13640                 }
13641                 if(PieceToChar(piece) == '+') {
13642                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13643                     *p++ = '+';
13644                     piece = (ChessSquare)(DEMOTED piece);
13645                 } 
13646                 *p++ = PieceToChar(piece);
13647                 if(p[-1] == '~') {
13648                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13649                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13650                     *p++ = '~';
13651                 }
13652             }
13653         }
13654         if (emptycount > 0) {
13655             if(emptycount<10) /* [HGM] can be >= 10 */
13656                 *p++ = '0' + emptycount;
13657             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13658             emptycount = 0;
13659         }
13660         *p++ = '/';
13661     }
13662     *(p - 1) = ' ';
13663
13664     /* [HGM] print Crazyhouse or Shogi holdings */
13665     if( gameInfo.holdingsWidth ) {
13666         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13667         q = p;
13668         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13669             piece = boards[move][i][BOARD_WIDTH-1];
13670             if( piece != EmptySquare )
13671               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13672                   *p++ = PieceToChar(piece);
13673         }
13674         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13675             piece = boards[move][BOARD_HEIGHT-i-1][0];
13676             if( piece != EmptySquare )
13677               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13678                   *p++ = PieceToChar(piece);
13679         }
13680
13681         if( q == p ) *p++ = '-';
13682         *p++ = ']';
13683         *p++ = ' ';
13684     }
13685
13686     /* Active color */
13687     *p++ = whiteToPlay ? 'w' : 'b';
13688     *p++ = ' ';
13689
13690   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13691     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13692   } else {
13693   if(nrCastlingRights) {
13694      q = p;
13695      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13696        /* [HGM] write directly from rights */
13697            if(castlingRights[move][2] >= 0 &&
13698               castlingRights[move][0] >= 0   )
13699                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13700            if(castlingRights[move][2] >= 0 &&
13701               castlingRights[move][1] >= 0   )
13702                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13703            if(castlingRights[move][5] >= 0 &&
13704               castlingRights[move][3] >= 0   )
13705                 *p++ = castlingRights[move][3] + AAA;
13706            if(castlingRights[move][5] >= 0 &&
13707               castlingRights[move][4] >= 0   )
13708                 *p++ = castlingRights[move][4] + AAA;
13709      } else {
13710
13711         /* [HGM] write true castling rights */
13712         if( nrCastlingRights == 6 ) {
13713             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13714                castlingRights[move][2] >= 0  ) *p++ = 'K';
13715             if(castlingRights[move][1] == BOARD_LEFT &&
13716                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13717             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13718                castlingRights[move][5] >= 0  ) *p++ = 'k';
13719             if(castlingRights[move][4] == BOARD_LEFT &&
13720                castlingRights[move][5] >= 0  ) *p++ = 'q';
13721         }
13722      }
13723      if (q == p) *p++ = '-'; /* No castling rights */
13724      *p++ = ' ';
13725   }
13726
13727   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13728      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13729     /* En passant target square */
13730     if (move > backwardMostMove) {
13731         fromX = moveList[move - 1][0] - AAA;
13732         fromY = moveList[move - 1][1] - ONE;
13733         toX = moveList[move - 1][2] - AAA;
13734         toY = moveList[move - 1][3] - ONE;
13735         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13736             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13737             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13738             fromX == toX) {
13739             /* 2-square pawn move just happened */
13740             *p++ = toX + AAA;
13741             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13742         } else {
13743             *p++ = '-';
13744         }
13745     } else if(move == backwardMostMove) {
13746         // [HGM] perhaps we should always do it like this, and forget the above?
13747         if(epStatus[move] >= 0) {
13748             *p++ = epStatus[move] + AAA;
13749             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13750         } else {
13751             *p++ = '-';
13752         }
13753     } else {
13754         *p++ = '-';
13755     }
13756     *p++ = ' ';
13757   }
13758   }
13759
13760     /* [HGM] find reversible plies */
13761     {   int i = 0, j=move;
13762
13763         if (appData.debugMode) { int k;
13764             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13765             for(k=backwardMostMove; k<=forwardMostMove; k++)
13766                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13767
13768         }
13769
13770         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13771         if( j == backwardMostMove ) i += initialRulePlies;
13772         sprintf(p, "%d ", i);
13773         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13774     }
13775     /* Fullmove number */
13776     sprintf(p, "%d", (move / 2) + 1);
13777     
13778     return StrSave(buf);
13779 }
13780
13781 Boolean
13782 ParseFEN(board, blackPlaysFirst, fen)
13783     Board board;
13784      int *blackPlaysFirst;
13785      char *fen;
13786 {
13787     int i, j;
13788     char *p;
13789     int emptycount;
13790     ChessSquare piece;
13791
13792     p = fen;
13793
13794     /* [HGM] by default clear Crazyhouse holdings, if present */
13795     if(gameInfo.holdingsWidth) {
13796        for(i=0; i<BOARD_HEIGHT; i++) {
13797            board[i][0]             = EmptySquare; /* black holdings */
13798            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13799            board[i][1]             = (ChessSquare) 0; /* black counts */
13800            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13801        }
13802     }
13803
13804     /* Piece placement data */
13805     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13806         j = 0;
13807         for (;;) {
13808             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13809                 if (*p == '/') p++;
13810                 emptycount = gameInfo.boardWidth - j;
13811                 while (emptycount--)
13812                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13813                 break;
13814 #if(BOARD_SIZE >= 10)
13815             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13816                 p++; emptycount=10;
13817                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13818                 while (emptycount--)
13819                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13820 #endif
13821             } else if (isdigit(*p)) {
13822                 emptycount = *p++ - '0';
13823                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13824                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13825                 while (emptycount--)
13826                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13827             } else if (*p == '+' || isalpha(*p)) {
13828                 if (j >= gameInfo.boardWidth) return FALSE;
13829                 if(*p=='+') {
13830                     piece = CharToPiece(*++p);
13831                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13832                     piece = (ChessSquare) (PROMOTED piece ); p++;
13833                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13834                 } else piece = CharToPiece(*p++);
13835
13836                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13837                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13838                     piece = (ChessSquare) (PROMOTED piece);
13839                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13840                     p++;
13841                 }
13842                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13843             } else {
13844                 return FALSE;
13845             }
13846         }
13847     }
13848     while (*p == '/' || *p == ' ') p++;
13849
13850     /* [HGM] look for Crazyhouse holdings here */
13851     while(*p==' ') p++;
13852     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13853         if(*p == '[') p++;
13854         if(*p == '-' ) *p++; /* empty holdings */ else {
13855             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13856             /* if we would allow FEN reading to set board size, we would   */
13857             /* have to add holdings and shift the board read so far here   */
13858             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13859                 *p++;
13860                 if((int) piece >= (int) BlackPawn ) {
13861                     i = (int)piece - (int)BlackPawn;
13862                     i = PieceToNumber((ChessSquare)i);
13863                     if( i >= gameInfo.holdingsSize ) return FALSE;
13864                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13865                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13866                 } else {
13867                     i = (int)piece - (int)WhitePawn;
13868                     i = PieceToNumber((ChessSquare)i);
13869                     if( i >= gameInfo.holdingsSize ) return FALSE;
13870                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13871                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13872                 }
13873             }
13874         }
13875         if(*p == ']') *p++;
13876     }
13877
13878     while(*p == ' ') p++;
13879
13880     /* Active color */
13881     switch (*p++) {
13882       case 'w':
13883         *blackPlaysFirst = FALSE;
13884         break;
13885       case 'b': 
13886         *blackPlaysFirst = TRUE;
13887         break;
13888       default:
13889         return FALSE;
13890     }
13891
13892     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13893     /* return the extra info in global variiables             */
13894
13895     /* set defaults in case FEN is incomplete */
13896     FENepStatus = EP_UNKNOWN;
13897     for(i=0; i<nrCastlingRights; i++ ) {
13898         FENcastlingRights[i] =
13899             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13900     }   /* assume possible unless obviously impossible */
13901     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13902     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13903     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13904                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13905     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13906     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13907     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13908                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13909     FENrulePlies = 0;
13910
13911     while(*p==' ') p++;
13912     if(nrCastlingRights) {
13913       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13914           /* castling indicator present, so default becomes no castlings */
13915           for(i=0; i<nrCastlingRights; i++ ) {
13916                  FENcastlingRights[i] = -1;
13917           }
13918       }
13919       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13920              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13921              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13922              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13923         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13924
13925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13926             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13927             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13928         }
13929         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13930             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13931         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13932                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13933         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13934                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13935         switch(c) {
13936           case'K':
13937               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13938               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13939               FENcastlingRights[2] = whiteKingFile;
13940               break;
13941           case'Q':
13942               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13943               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13944               FENcastlingRights[2] = whiteKingFile;
13945               break;
13946           case'k':
13947               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13948               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13949               FENcastlingRights[5] = blackKingFile;
13950               break;
13951           case'q':
13952               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13953               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13954               FENcastlingRights[5] = blackKingFile;
13955           case '-':
13956               break;
13957           default: /* FRC castlings */
13958               if(c >= 'a') { /* black rights */
13959                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13960                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13961                   if(i == BOARD_RGHT) break;
13962                   FENcastlingRights[5] = i;
13963                   c -= AAA;
13964                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13965                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13966                   if(c > i)
13967                       FENcastlingRights[3] = c;
13968                   else
13969                       FENcastlingRights[4] = c;
13970               } else { /* white rights */
13971                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13972                     if(board[0][i] == WhiteKing) break;
13973                   if(i == BOARD_RGHT) break;
13974                   FENcastlingRights[2] = i;
13975                   c -= AAA - 'a' + 'A';
13976                   if(board[0][c] >= WhiteKing) break;
13977                   if(c > i)
13978                       FENcastlingRights[0] = c;
13979                   else
13980                       FENcastlingRights[1] = c;
13981               }
13982         }
13983       }
13984       for(i=0; i<nrCastlingRights; i++)
13985         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
13986     if (appData.debugMode) {
13987         fprintf(debugFP, "FEN castling rights:");
13988         for(i=0; i<nrCastlingRights; i++)
13989         fprintf(debugFP, " %d", FENcastlingRights[i]);
13990         fprintf(debugFP, "\n");
13991     }
13992
13993       while(*p==' ') p++;
13994     }
13995
13996     /* read e.p. field in games that know e.p. capture */
13997     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13998        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13999       if(*p=='-') {
14000         p++; FENepStatus = EP_NONE;
14001       } else {
14002          char c = *p++ - AAA;
14003
14004          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14005          if(*p >= '0' && *p <='9') *p++;
14006          FENepStatus = c;
14007       }
14008     }
14009
14010
14011     if(sscanf(p, "%d", &i) == 1) {
14012         FENrulePlies = i; /* 50-move ply counter */
14013         /* (The move number is still ignored)    */
14014     }
14015
14016     return TRUE;
14017 }
14018       
14019 void
14020 EditPositionPasteFEN(char *fen)
14021 {
14022   if (fen != NULL) {
14023     Board initial_position;
14024
14025     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14026       DisplayError(_("Bad FEN position in clipboard"), 0);
14027       return ;
14028     } else {
14029       int savedBlackPlaysFirst = blackPlaysFirst;
14030       EditPositionEvent();
14031       blackPlaysFirst = savedBlackPlaysFirst;
14032       CopyBoard(boards[0], initial_position);
14033           /* [HGM] copy FEN attributes as well */
14034           {   int i;
14035               initialRulePlies = FENrulePlies;
14036               epStatus[0] = FENepStatus;
14037               for( i=0; i<nrCastlingRights; i++ )
14038                   castlingRights[0][i] = FENcastlingRights[i];
14039           }
14040       EditPositionDone(FALSE);
14041       DisplayBothClocks();
14042       DrawPosition(FALSE, boards[currentMove]);
14043     }
14044   }
14045 }
14046
14047 static char cseq[12] = "\\   ";
14048
14049 Boolean set_cont_sequence(char *new_seq)
14050 {
14051     int len;
14052     Boolean ret;
14053
14054     // handle bad attempts to set the sequence
14055         if (!new_seq)
14056                 return 0; // acceptable error - no debug
14057
14058     len = strlen(new_seq);
14059     ret = (len > 0) && (len < sizeof(cseq));
14060     if (ret)
14061         strcpy(cseq, new_seq);
14062     else if (appData.debugMode)
14063         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14064     return ret;
14065 }
14066
14067 /*
14068     reformat a source message so words don't cross the width boundary.  internal
14069     newlines are not removed.  returns the wrapped size (no null character unless
14070     included in source message).  If dest is NULL, only calculate the size required
14071     for the dest buffer.  lp argument indicats line position upon entry, and it's
14072     passed back upon exit.
14073 */
14074 int wrap(char *dest, char *src, int count, int width, int *lp)
14075 {
14076     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14077
14078     cseq_len = strlen(cseq);
14079     old_line = line = *lp;
14080     ansi = len = clen = 0;
14081
14082     for (i=0; i < count; i++)
14083     {
14084         if (src[i] == '\033')
14085             ansi = 1;
14086
14087         // if we hit the width, back up
14088         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14089         {
14090             // store i & len in case the word is too long
14091             old_i = i, old_len = len;
14092
14093             // find the end of the last word
14094             while (i && src[i] != ' ' && src[i] != '\n')
14095             {
14096                 i--;
14097                 len--;
14098             }
14099
14100             // word too long?  restore i & len before splitting it
14101             if ((old_i-i+clen) >= width)
14102             {
14103                 i = old_i;
14104                 len = old_len;
14105             }
14106
14107             // extra space?
14108             if (i && src[i-1] == ' ')
14109                 len--;
14110
14111             if (src[i] != ' ' && src[i] != '\n')
14112             {
14113                 i--;
14114                 if (len)
14115                     len--;
14116             }
14117
14118             // now append the newline and continuation sequence
14119             if (dest)
14120                 dest[len] = '\n';
14121             len++;
14122             if (dest)
14123                 strncpy(dest+len, cseq, cseq_len);
14124             len += cseq_len;
14125             line = cseq_len;
14126             clen = cseq_len;
14127             continue;
14128         }
14129
14130         if (dest)
14131             dest[len] = src[i];
14132         len++;
14133         if (!ansi)
14134             line++;
14135         if (src[i] == '\n')
14136             line = 0;
14137         if (src[i] == 'm')
14138             ansi = 0;
14139     }
14140     if (dest && appData.debugMode)
14141     {
14142         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14143             count, width, line, len, *lp);
14144         show_bytes(debugFP, src, count);
14145         fprintf(debugFP, "\ndest: ");
14146         show_bytes(debugFP, dest, len);
14147         fprintf(debugFP, "\n");
14148     }
14149     *lp = dest ? line : old_line;
14150
14151     return len;
14152 }