another bug in VriantSwitch: an unitialized board was printed.
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     set_cont_sequence(appData.wrapContSeq);
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908         return; // prevent overwriting by pre-board holdings
1909
1910     if( (int)lowestPiece >= BlackPawn ) {
1911         holdingsColumn = 0;
1912         countsColumn = 1;
1913         holdingsStartRow = BOARD_HEIGHT-1;
1914         direction = -1;
1915     } else {
1916         holdingsColumn = BOARD_WIDTH-1;
1917         countsColumn = BOARD_WIDTH-2;
1918         holdingsStartRow = 0;
1919         direction = 1;
1920     }
1921
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923         board[i][holdingsColumn] = EmptySquare;
1924         board[i][countsColumn]   = (ChessSquare) 0;
1925     }
1926     while( (p=*holdings++) != NULLCHAR ) {
1927         piece = CharToPiece( ToUpper(p) );
1928         if(piece == EmptySquare) continue;
1929         /*j = (int) piece - (int) WhitePawn;*/
1930         j = PieceToNumber(piece);
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932         if(j < 0) continue;               /* should not happen */
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935         board[holdingsStartRow+j*direction][countsColumn]++;
1936     }
1937 }
1938
1939
1940 void
1941 VariantSwitch(Board board, VariantClass newVariant)
1942 {
1943    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944    Board oldBoard;
1945
1946    startedFromPositionFile = FALSE;
1947    if(gameInfo.variant == newVariant) return;
1948
1949    /* [HGM] This routine is called each time an assignment is made to
1950     * gameInfo.variant during a game, to make sure the board sizes
1951     * are set to match the new variant. If that means adding or deleting
1952     * holdings, we shift the playing board accordingly
1953     * This kludge is needed because in ICS observe mode, we get boards
1954     * of an ongoing game without knowing the variant, and learn about the
1955     * latter only later. This can be because of the move list we requested,
1956     * in which case the game history is refilled from the beginning anyway,
1957     * but also when receiving holdings of a crazyhouse game. In the latter
1958     * case we want to add those holdings to the already received position.
1959     */
1960
1961    
1962    if (appData.debugMode) {
1963      fprintf(debugFP, "Switch board from %s to %s\n",
1964              VariantName(gameInfo.variant), VariantName(newVariant));
1965      setbuf(debugFP, NULL);
1966    }
1967    shuffleOpenings = 0;       /* [HGM] shuffle */
1968    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969    switch(newVariant) 
1970      {
1971      case VariantShogi:
1972        newWidth = 9;  newHeight = 9;
1973        gameInfo.holdingsSize = 7;
1974      case VariantBughouse:
1975      case VariantCrazyhouse:
1976        newHoldingsWidth = 2; break;
1977      case VariantGreat:
1978        newWidth = 10;
1979      case VariantSuper:
1980        newHoldingsWidth = 2;
1981        gameInfo.holdingsSize = 8;
1982        break;
1983      case VariantGothic:
1984      case VariantCapablanca:
1985      case VariantCapaRandom:
1986        newWidth = 10;
1987      default:
1988        newHoldingsWidth = gameInfo.holdingsSize = 0;
1989      };
1990    
1991    if(newWidth  != gameInfo.boardWidth  ||
1992       newHeight != gameInfo.boardHeight ||
1993       newHoldingsWidth != gameInfo.holdingsWidth ) {
1994      
1995      /* shift position to new playing area, if needed */
1996      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997        for(i=0; i<BOARD_HEIGHT; i++) 
1998          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000              board[i][j];
2001        for(i=0; i<newHeight; i++) {
2002          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004        }
2005      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006        for(i=0; i<BOARD_HEIGHT; i++)
2007          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009              board[i][j];
2010      }
2011      gameInfo.boardWidth  = newWidth;
2012      gameInfo.boardHeight = newHeight;
2013      gameInfo.holdingsWidth = newHoldingsWidth;
2014      gameInfo.variant = newVariant;
2015      InitDrawingSizes(-2, 0);
2016    } else gameInfo.variant = newVariant;
2017    CopyBoard(oldBoard, board);   // remember correctly formatted board
2018      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2019    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2020 }
2021
2022 static int loggedOn = FALSE;
2023
2024 /*-- Game start info cache: --*/
2025 int gs_gamenum;
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\   ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2033
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2036
2037 void
2038 read_from_ics(isr, closure, data, count, error)
2039      InputSourceRef isr;
2040      VOIDSTAR closure;
2041      char *data;
2042      int count;
2043      int error;
2044 {
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2054     
2055     static int started = STARTED_NONE;
2056     static char parse[20000];
2057     static int parse_pos = 0;
2058     static char buf[BUF_SIZE + 1];
2059     static int firstTime = TRUE, intfSet = FALSE;
2060     static ColorClass prevColor = ColorNormal;
2061     static int savingComment = FALSE;
2062     static int cmatch = 0; // continuation sequence match
2063     char *bp;
2064     char str[500];
2065     int i, oldi;
2066     int buf_len;
2067     int next_out;
2068     int tkind;
2069     int backup;    /* [DM] For zippy color lines */
2070     char *p;
2071     char talker[MSG_SIZ]; // [HGM] chat
2072     int channel;
2073
2074     if (appData.debugMode) {
2075       if (!error) {
2076         fprintf(debugFP, "<ICS: ");
2077         show_bytes(debugFP, data, count);
2078         fprintf(debugFP, "\n");
2079       }
2080     }
2081
2082     if (appData.debugMode) { int f = forwardMostMove;
2083         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085     }
2086     if (count > 0) {
2087         /* If last read ended with a partial line that we couldn't parse,
2088            prepend it to the new read and try again. */
2089         if (leftover_len > 0) {
2090             for (i=0; i<leftover_len; i++)
2091               buf[i] = buf[leftover_start + i];
2092         }
2093
2094     /* copy new characters into the buffer */
2095     bp = buf + leftover_len;
2096     buf_len=leftover_len;
2097     for (i=0; i<count; i++)
2098     {
2099         // ignore these
2100         if (data[i] == '\r')
2101             continue;
2102
2103         // join lines split by ICS?
2104         if (!appData.noJoin)
2105         {
2106             /*
2107                 Joining just consists of finding matches against the
2108                 continuation sequence, and discarding that sequence
2109                 if found instead of copying it.  So, until a match
2110                 fails, there's nothing to do since it might be the
2111                 complete sequence, and thus, something we don't want
2112                 copied.
2113             */
2114             if (data[i] == cont_seq[cmatch])
2115             {
2116                 cmatch++;
2117                 if (cmatch == strlen(cont_seq))
2118                 {
2119                     cmatch = 0; // complete match.  just reset the counter
2120
2121                     /*
2122                         it's possible for the ICS to not include the space
2123                         at the end of the last word, making our [correct]
2124                         join operation fuse two separate words.  the server
2125                         does this when the space occurs at the width setting.
2126                     */
2127                     if (!buf_len || buf[buf_len-1] != ' ')
2128                     {
2129                         *bp++ = ' ';
2130                         buf_len++;
2131                     }
2132                 }
2133                 continue;
2134             }
2135             else if (cmatch)
2136             {
2137                 /*
2138                     match failed, so we have to copy what matched before
2139                     falling through and copying this character.  In reality,
2140                     this will only ever be just the newline character, but
2141                     it doesn't hurt to be precise.
2142                 */
2143                 strncpy(bp, cont_seq, cmatch);
2144                 bp += cmatch;
2145                 buf_len += cmatch;
2146                 cmatch = 0;
2147             }
2148         }
2149
2150         // copy this char
2151         *bp++ = data[i];
2152         buf_len++;
2153     }
2154
2155         buf[buf_len] = NULLCHAR;
2156         next_out = leftover_len;
2157         leftover_start = 0;
2158         
2159         i = 0;
2160         while (i < buf_len) {
2161             /* Deal with part of the TELNET option negotiation
2162                protocol.  We refuse to do anything beyond the
2163                defaults, except that we allow the WILL ECHO option,
2164                which ICS uses to turn off password echoing when we are
2165                directly connected to it.  We reject this option
2166                if localLineEditing mode is on (always on in xboard)
2167                and we are talking to port 23, which might be a real
2168                telnet server that will try to keep WILL ECHO on permanently.
2169              */
2170             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172                 unsigned char option;
2173                 oldi = i;
2174                 switch ((unsigned char) buf[++i]) {
2175                   case TN_WILL:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<WILL ");
2178                     switch (option = (unsigned char) buf[++i]) {
2179                       case TN_ECHO:
2180                         if (appData.debugMode)
2181                           fprintf(debugFP, "ECHO ");
2182                         /* Reply only if this is a change, according
2183                            to the protocol rules. */
2184                         if (remoteEchoOption) break;
2185                         if (appData.localLineEditing &&
2186                             atoi(appData.icsPort) == TN_PORT) {
2187                             TelnetRequest(TN_DONT, TN_ECHO);
2188                         } else {
2189                             EchoOff();
2190                             TelnetRequest(TN_DO, TN_ECHO);
2191                             remoteEchoOption = TRUE;
2192                         }
2193                         break;
2194                       default:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "%d ", option);
2197                         /* Whatever this is, we don't want it. */
2198                         TelnetRequest(TN_DONT, option);
2199                         break;
2200                     }
2201                     break;
2202                   case TN_WONT:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<WONT ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       case TN_ECHO:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "ECHO ");
2209                         /* Reply only if this is a change, according
2210                            to the protocol rules. */
2211                         if (!remoteEchoOption) break;
2212                         EchoOn();
2213                         TelnetRequest(TN_DONT, TN_ECHO);
2214                         remoteEchoOption = FALSE;
2215                         break;
2216                       default:
2217                         if (appData.debugMode)
2218                           fprintf(debugFP, "%d ", (unsigned char) option);
2219                         /* Whatever this is, it must already be turned
2220                            off, because we never agree to turn on
2221                            anything non-default, so according to the
2222                            protocol rules, we don't reply. */
2223                         break;
2224                     }
2225                     break;
2226                   case TN_DO:
2227                     if (appData.debugMode)
2228                       fprintf(debugFP, "\n<DO ");
2229                     switch (option = (unsigned char) buf[++i]) {
2230                       default:
2231                         /* Whatever this is, we refuse to do it. */
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", option);
2234                         TelnetRequest(TN_WONT, option);
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DONT:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DONT ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         if (appData.debugMode)
2244                           fprintf(debugFP, "%d ", option);
2245                         /* Whatever this is, we are already not doing
2246                            it, because we never agree to do anything
2247                            non-default, so according to the protocol
2248                            rules, we don't reply. */
2249                         break;
2250                     }
2251                     break;
2252                   case TN_IAC:
2253                     if (appData.debugMode)
2254                       fprintf(debugFP, "\n<IAC ");
2255                     /* Doubled IAC; pass it through */
2256                     i--;
2257                     break;
2258                   default:
2259                     if (appData.debugMode)
2260                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261                     /* Drop all other telnet commands on the floor */
2262                     break;
2263                 }
2264                 if (oldi > next_out)
2265                   SendToPlayer(&buf[next_out], oldi - next_out);
2266                 if (++i > next_out)
2267                   next_out = i;
2268                 continue;
2269             }
2270                 
2271             /* OK, this at least will *usually* work */
2272             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273                 loggedOn = TRUE;
2274             }
2275             
2276             if (loggedOn && !intfSet) {
2277                 if (ics_type == ICS_ICC) {
2278                   sprintf(str,
2279                           "/set-quietly interface %s\n/set-quietly style 12\n",
2280                           programVersion);
2281                 } else if (ics_type == ICS_CHESSNET) {
2282                   sprintf(str, "/style 12\n");
2283                 } else {
2284                   strcpy(str, "alias $ @\n$set interface ");
2285                   strcat(str, programVersion);
2286                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2287 #ifdef WIN32
2288                   strcat(str, "$iset nohighlight 1\n");
2289 #endif
2290                   strcat(str, "$iset lock 1\n$style 12\n");
2291                 }
2292                 SendToICS(str);
2293                 NotifyFrontendLogin();
2294                 intfSet = TRUE;
2295             }
2296
2297             if (started == STARTED_COMMENT) {
2298                 /* Accumulate characters in comment */
2299                 parse[parse_pos++] = buf[i];
2300                 if (buf[i] == '\n') {
2301                     parse[parse_pos] = NULLCHAR;
2302                     if(chattingPartner>=0) {
2303                         char mess[MSG_SIZ];
2304                         sprintf(mess, "%s%s", talker, parse);
2305                         OutputChatMessage(chattingPartner, mess);
2306                         chattingPartner = -1;
2307                     } else
2308                     if(!suppressKibitz) // [HGM] kibitz
2309                         AppendComment(forwardMostMove, StripHighlight(parse));
2310                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311                         int nrDigit = 0, nrAlph = 0, i;
2312                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314                         parse[parse_pos] = NULLCHAR;
2315                         // try to be smart: if it does not look like search info, it should go to
2316                         // ICS interaction window after all, not to engine-output window.
2317                         for(i=0; i<parse_pos; i++) { // count letters and digits
2318                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2320                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2321                         }
2322                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323                             int depth=0; float score;
2324                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326                                 pvInfoList[forwardMostMove-1].depth = depth;
2327                                 pvInfoList[forwardMostMove-1].score = 100*score;
2328                             }
2329                             OutputKibitz(suppressKibitz, parse);
2330                         } else {
2331                             char tmp[MSG_SIZ];
2332                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333                             SendToPlayer(tmp, strlen(tmp));
2334                         }
2335                     }
2336                     started = STARTED_NONE;
2337                 } else {
2338                     /* Don't match patterns against characters in chatter */
2339                     i++;
2340                     continue;
2341                 }
2342             }
2343             if (started == STARTED_CHATTER) {
2344                 if (buf[i] != '\n') {
2345                     /* Don't match patterns against characters in chatter */
2346                     i++;
2347                     continue;
2348                 }
2349                 started = STARTED_NONE;
2350             }
2351
2352             /* Kludge to deal with rcmd protocol */
2353             if (firstTime && looking_at(buf, &i, "\001*")) {
2354                 DisplayFatalError(&buf[1], 0, 1);
2355                 continue;
2356             } else {
2357                 firstTime = FALSE;
2358             }
2359
2360             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361                 ics_type = ICS_ICC;
2362                 ics_prefix = "/";
2363                 if (appData.debugMode)
2364                   fprintf(debugFP, "ics_type %d\n", ics_type);
2365                 continue;
2366             }
2367             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368                 ics_type = ICS_FICS;
2369                 ics_prefix = "$";
2370                 if (appData.debugMode)
2371                   fprintf(debugFP, "ics_type %d\n", ics_type);
2372                 continue;
2373             }
2374             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375                 ics_type = ICS_CHESSNET;
2376                 ics_prefix = "/";
2377                 if (appData.debugMode)
2378                   fprintf(debugFP, "ics_type %d\n", ics_type);
2379                 continue;
2380             }
2381
2382             if (!loggedOn &&
2383                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2385                  looking_at(buf, &i, "will be \"*\""))) {
2386               strcpy(ics_handle, star_match[0]);
2387               continue;
2388             }
2389
2390             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2391               char buf[MSG_SIZ];
2392               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393               DisplayIcsInteractionTitle(buf);
2394               have_set_title = TRUE;
2395             }
2396
2397             /* skip finger notes */
2398             if (started == STARTED_NONE &&
2399                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400                  (buf[i] == '1' && buf[i+1] == '0')) &&
2401                 buf[i+2] == ':' && buf[i+3] == ' ') {
2402               started = STARTED_CHATTER;
2403               i += 3;
2404               continue;
2405             }
2406
2407             /* skip formula vars */
2408             if (started == STARTED_NONE &&
2409                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410               started = STARTED_CHATTER;
2411               i += 3;
2412               continue;
2413             }
2414
2415             oldi = i;
2416             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417             if (appData.autoKibitz && started == STARTED_NONE && 
2418                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2419                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420                 if(looking_at(buf, &i, "* kibitzes: ") &&
2421                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2422                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2423                         suppressKibitz = TRUE;
2424                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425                                 && (gameMode == IcsPlayingWhite)) ||
2426                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2428                             started = STARTED_CHATTER; // own kibitz we simply discard
2429                         else {
2430                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431                             parse_pos = 0; parse[0] = NULLCHAR;
2432                             savingComment = TRUE;
2433                             suppressKibitz = gameMode != IcsObserving ? 2 :
2434                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435                         } 
2436                         continue;
2437                 } else
2438                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439                     started = STARTED_CHATTER;
2440                     suppressKibitz = TRUE;
2441                 }
2442             } // [HGM] kibitz: end of patch
2443
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2445
2446             // [HGM] chat: intercept tells by users for which we have an open chat window
2447             channel = -1;
2448             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2449                                            looking_at(buf, &i, "* whispers:") ||
2450                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2452                 int p;
2453                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454                 chattingPartner = -1;
2455
2456                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457                 for(p=0; p<MAX_CHAT; p++) {
2458                     if(channel == atoi(chatPartner[p])) {
2459                     talker[0] = '['; strcat(talker, "]");
2460                     chattingPartner = p; break;
2461                     }
2462                 } else
2463                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464                 for(p=0; p<MAX_CHAT; p++) {
2465                     if(!strcmp("WHISPER", chatPartner[p])) {
2466                         talker[0] = '['; strcat(talker, "]");
2467                         chattingPartner = p; break;
2468                     }
2469                 }
2470                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2472                     talker[0] = 0;
2473                     chattingPartner = p; break;
2474                 }
2475                 if(chattingPartner<0) i = oldi; else {
2476                     started = STARTED_COMMENT;
2477                     parse_pos = 0; parse[0] = NULLCHAR;
2478                     savingComment = TRUE;
2479                     suppressKibitz = TRUE;
2480                 }
2481             } // [HGM] chat: end of patch
2482
2483             if (appData.zippyTalk || appData.zippyPlay) {
2484                 /* [DM] Backup address for color zippy lines */
2485                 backup = i;
2486 #if ZIPPY
2487        #ifdef WIN32
2488                if (loggedOn == TRUE)
2489                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2491        #else
2492                 if (ZippyControl(buf, &i) ||
2493                     ZippyConverse(buf, &i) ||
2494                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2495                       loggedOn = TRUE;
2496                       if (!appData.colorize) continue;
2497                 }
2498        #endif
2499 #endif
2500             } // [DM] 'else { ' deleted
2501                 if (
2502                     /* Regular tells and says */
2503                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2505                     looking_at(buf, &i, "* says: ") ||
2506                     /* Don't color "message" or "messages" output */
2507                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508                     looking_at(buf, &i, "*. * at *:*: ") ||
2509                     looking_at(buf, &i, "--* (*:*): ") ||
2510                     /* Message notifications (same color as tells) */
2511                     looking_at(buf, &i, "* has left a message ") ||
2512                     looking_at(buf, &i, "* just sent you a message:\n") ||
2513                     /* Whispers and kibitzes */
2514                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515                     looking_at(buf, &i, "* kibitzes: ") ||
2516                     /* Channel tells */
2517                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2518
2519                   if (tkind == 1 && strchr(star_match[0], ':')) {
2520                       /* Avoid "tells you:" spoofs in channels */
2521                      tkind = 3;
2522                   }
2523                   if (star_match[0][0] == NULLCHAR ||
2524                       strchr(star_match[0], ' ') ||
2525                       (tkind == 3 && strchr(star_match[1], ' '))) {
2526                     /* Reject bogus matches */
2527                     i = oldi;
2528                   } else {
2529                     if (appData.colorize) {
2530                       if (oldi > next_out) {
2531                         SendToPlayer(&buf[next_out], oldi - next_out);
2532                         next_out = oldi;
2533                       }
2534                       switch (tkind) {
2535                       case 1:
2536                         Colorize(ColorTell, FALSE);
2537                         curColor = ColorTell;
2538                         break;
2539                       case 2:
2540                         Colorize(ColorKibitz, FALSE);
2541                         curColor = ColorKibitz;
2542                         break;
2543                       case 3:
2544                         p = strrchr(star_match[1], '(');
2545                         if (p == NULL) {
2546                           p = star_match[1];
2547                         } else {
2548                           p++;
2549                         }
2550                         if (atoi(p) == 1) {
2551                           Colorize(ColorChannel1, FALSE);
2552                           curColor = ColorChannel1;
2553                         } else {
2554                           Colorize(ColorChannel, FALSE);
2555                           curColor = ColorChannel;
2556                         }
2557                         break;
2558                       case 5:
2559                         curColor = ColorNormal;
2560                         break;
2561                       }
2562                     }
2563                     if (started == STARTED_NONE && appData.autoComment &&
2564                         (gameMode == IcsObserving ||
2565                          gameMode == IcsPlayingWhite ||
2566                          gameMode == IcsPlayingBlack)) {
2567                       parse_pos = i - oldi;
2568                       memcpy(parse, &buf[oldi], parse_pos);
2569                       parse[parse_pos] = NULLCHAR;
2570                       started = STARTED_COMMENT;
2571                       savingComment = TRUE;
2572                     } else {
2573                       started = STARTED_CHATTER;
2574                       savingComment = FALSE;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                   }
2579                 }
2580
2581                 if (looking_at(buf, &i, "* s-shouts: ") ||
2582                     looking_at(buf, &i, "* c-shouts: ")) {
2583                     if (appData.colorize) {
2584                         if (oldi > next_out) {
2585                             SendToPlayer(&buf[next_out], oldi - next_out);
2586                             next_out = oldi;
2587                         }
2588                         Colorize(ColorSShout, FALSE);
2589                         curColor = ColorSShout;
2590                     }
2591                     loggedOn = TRUE;
2592                     started = STARTED_CHATTER;
2593                     continue;
2594                 }
2595
2596                 if (looking_at(buf, &i, "--->")) {
2597                     loggedOn = TRUE;
2598                     continue;
2599                 }
2600
2601                 if (looking_at(buf, &i, "* shouts: ") ||
2602                     looking_at(buf, &i, "--> ")) {
2603                     if (appData.colorize) {
2604                         if (oldi > next_out) {
2605                             SendToPlayer(&buf[next_out], oldi - next_out);
2606                             next_out = oldi;
2607                         }
2608                         Colorize(ColorShout, FALSE);
2609                         curColor = ColorShout;
2610                     }
2611                     loggedOn = TRUE;
2612                     started = STARTED_CHATTER;
2613                     continue;
2614                 }
2615
2616                 if (looking_at( buf, &i, "Challenge:")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorChallenge, FALSE);
2623                         curColor = ColorChallenge;
2624                     }
2625                     loggedOn = TRUE;
2626                     continue;
2627                 }
2628
2629                 if (looking_at(buf, &i, "* offers you") ||
2630                     looking_at(buf, &i, "* offers to be") ||
2631                     looking_at(buf, &i, "* would like to") ||
2632                     looking_at(buf, &i, "* requests to") ||
2633                     looking_at(buf, &i, "Your opponent offers") ||
2634                     looking_at(buf, &i, "Your opponent requests")) {
2635
2636                     if (appData.colorize) {
2637                         if (oldi > next_out) {
2638                             SendToPlayer(&buf[next_out], oldi - next_out);
2639                             next_out = oldi;
2640                         }
2641                         Colorize(ColorRequest, FALSE);
2642                         curColor = ColorRequest;
2643                     }
2644                     continue;
2645                 }
2646
2647                 if (looking_at(buf, &i, "* (*) seeking")) {
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorSeek, FALSE);
2654                         curColor = ColorSeek;
2655                     }
2656                     continue;
2657             }
2658
2659             if (looking_at(buf, &i, "\\   ")) {
2660                 if (prevColor != ColorNormal) {
2661                     if (oldi > next_out) {
2662                         SendToPlayer(&buf[next_out], oldi - next_out);
2663                         next_out = oldi;
2664                     }
2665                     Colorize(prevColor, TRUE);
2666                     curColor = prevColor;
2667                 }
2668                 if (savingComment) {
2669                     parse_pos = i - oldi;
2670                     memcpy(parse, &buf[oldi], parse_pos);
2671                     parse[parse_pos] = NULLCHAR;
2672                     started = STARTED_COMMENT;
2673                 } else {
2674                     started = STARTED_CHATTER;
2675                 }
2676                 continue;
2677             }
2678
2679             if (looking_at(buf, &i, "Black Strength :") ||
2680                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681                 looking_at(buf, &i, "<10>") ||
2682                 looking_at(buf, &i, "#@#")) {
2683                 /* Wrong board style */
2684                 loggedOn = TRUE;
2685                 SendToICS(ics_prefix);
2686                 SendToICS("set style 12\n");
2687                 SendToICS(ics_prefix);
2688                 SendToICS("refresh\n");
2689                 continue;
2690             }
2691             
2692             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2693                 ICSInitScript();
2694                 have_sent_ICS_logon = 1;
2695                 continue;
2696             }
2697               
2698             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2699                 (looking_at(buf, &i, "\n<12> ") ||
2700                  looking_at(buf, &i, "<12> "))) {
2701                 loggedOn = TRUE;
2702                 if (oldi > next_out) {
2703                     SendToPlayer(&buf[next_out], oldi - next_out);
2704                 }
2705                 next_out = i;
2706                 started = STARTED_BOARD;
2707                 parse_pos = 0;
2708                 continue;
2709             }
2710
2711             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712                 looking_at(buf, &i, "<b1> ")) {
2713                 if (oldi > next_out) {
2714                     SendToPlayer(&buf[next_out], oldi - next_out);
2715                 }
2716                 next_out = i;
2717                 started = STARTED_HOLDINGS;
2718                 parse_pos = 0;
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2723                 loggedOn = TRUE;
2724                 /* Header for a move list -- first line */
2725
2726                 switch (ics_getting_history) {
2727                   case H_FALSE:
2728                     switch (gameMode) {
2729                       case IcsIdle:
2730                       case BeginningOfGame:
2731                         /* User typed "moves" or "oldmoves" while we
2732                            were idle.  Pretend we asked for these
2733                            moves and soak them up so user can step
2734                            through them and/or save them.
2735                            */
2736                         Reset(FALSE, TRUE);
2737                         gameMode = IcsObserving;
2738                         ModeHighlight();
2739                         ics_gamenum = -1;
2740                         ics_getting_history = H_GOT_UNREQ_HEADER;
2741                         break;
2742                       case EditGame: /*?*/
2743                       case EditPosition: /*?*/
2744                         /* Should above feature work in these modes too? */
2745                         /* For now it doesn't */
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                       default:
2749                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2750                         break;
2751                     }
2752                     break;
2753                   case H_REQUESTED:
2754                     /* Is this the right one? */
2755                     if (gameInfo.white && gameInfo.black &&
2756                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2757                         strcmp(gameInfo.black, star_match[2]) == 0) {
2758                         /* All is well */
2759                         ics_getting_history = H_GOT_REQ_HEADER;
2760                     }
2761                     break;
2762                   case H_GOT_REQ_HEADER:
2763                   case H_GOT_UNREQ_HEADER:
2764                   case H_GOT_UNWANTED_HEADER:
2765                   case H_GETTING_MOVES:
2766                     /* Should not happen */
2767                     DisplayError(_("Error gathering move list: two headers"), 0);
2768                     ics_getting_history = H_FALSE;
2769                     break;
2770                 }
2771
2772                 /* Save player ratings into gameInfo if needed */
2773                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775                     (gameInfo.whiteRating == -1 ||
2776                      gameInfo.blackRating == -1)) {
2777
2778                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2779                     gameInfo.blackRating = string_to_rating(star_match[3]);
2780                     if (appData.debugMode)
2781                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2782                               gameInfo.whiteRating, gameInfo.blackRating);
2783                 }
2784                 continue;
2785             }
2786
2787             if (looking_at(buf, &i,
2788               "* * match, initial time: * minute*, increment: * second")) {
2789                 /* Header for a move list -- second line */
2790                 /* Initial board will follow if this is a wild game */
2791                 if (gameInfo.event != NULL) free(gameInfo.event);
2792                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793                 gameInfo.event = StrSave(str);
2794                 /* [HGM] we switched variant. Translate boards if needed. */
2795                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i, "Move  ")) {
2800                 /* Beginning of a move list */
2801                 switch (ics_getting_history) {
2802                   case H_FALSE:
2803                     /* Normally should not happen */
2804                     /* Maybe user hit reset while we were parsing */
2805                     break;
2806                   case H_REQUESTED:
2807                     /* Happens if we are ignoring a move list that is not
2808                      * the one we just requested.  Common if the user
2809                      * tries to observe two games without turning off
2810                      * getMoveList */
2811                     break;
2812                   case H_GETTING_MOVES:
2813                     /* Should not happen */
2814                     DisplayError(_("Error gathering move list: nested"), 0);
2815                     ics_getting_history = H_FALSE;
2816                     break;
2817                   case H_GOT_REQ_HEADER:
2818                     ics_getting_history = H_GETTING_MOVES;
2819                     started = STARTED_MOVES;
2820                     parse_pos = 0;
2821                     if (oldi > next_out) {
2822                         SendToPlayer(&buf[next_out], oldi - next_out);
2823                     }
2824                     break;
2825                   case H_GOT_UNREQ_HEADER:
2826                     ics_getting_history = H_GETTING_MOVES;
2827                     started = STARTED_MOVES_NOHIDE;
2828                     parse_pos = 0;
2829                     break;
2830                   case H_GOT_UNWANTED_HEADER:
2831                     ics_getting_history = H_FALSE;
2832                     break;
2833                 }
2834                 continue;
2835             }                           
2836             
2837             if (looking_at(buf, &i, "% ") ||
2838                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840                 savingComment = FALSE;
2841                 switch (started) {
2842                   case STARTED_MOVES:
2843                   case STARTED_MOVES_NOHIDE:
2844                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845                     parse[parse_pos + i - oldi] = NULLCHAR;
2846                     ParseGameHistory(parse);
2847 #if ZIPPY
2848                     if (appData.zippyPlay && first.initDone) {
2849                         FeedMovesToProgram(&first, forwardMostMove);
2850                         if (gameMode == IcsPlayingWhite) {
2851                             if (WhiteOnMove(forwardMostMove)) {
2852                                 if (first.sendTime) {
2853                                   if (first.useColors) {
2854                                     SendToProgram("black\n", &first); 
2855                                   }
2856                                   SendTimeRemaining(&first, TRUE);
2857                                 }
2858                                 if (first.useColors) {
2859                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2860                                 }
2861                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862                                 first.maybeThinking = TRUE;
2863                             } else {
2864                                 if (first.usePlayother) {
2865                                   if (first.sendTime) {
2866                                     SendTimeRemaining(&first, TRUE);
2867                                   }
2868                                   SendToProgram("playother\n", &first);
2869                                   firstMove = FALSE;
2870                                 } else {
2871                                   firstMove = TRUE;
2872                                 }
2873                             }
2874                         } else if (gameMode == IcsPlayingBlack) {
2875                             if (!WhiteOnMove(forwardMostMove)) {
2876                                 if (first.sendTime) {
2877                                   if (first.useColors) {
2878                                     SendToProgram("white\n", &first);
2879                                   }
2880                                   SendTimeRemaining(&first, FALSE);
2881                                 }
2882                                 if (first.useColors) {
2883                                   SendToProgram("black\n", &first);
2884                                 }
2885                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886                                 first.maybeThinking = TRUE;
2887                             } else {
2888                                 if (first.usePlayother) {
2889                                   if (first.sendTime) {
2890                                     SendTimeRemaining(&first, FALSE);
2891                                   }
2892                                   SendToProgram("playother\n", &first);
2893                                   firstMove = FALSE;
2894                                 } else {
2895                                   firstMove = TRUE;
2896                                 }
2897                             }
2898                         }                       
2899                     }
2900 #endif
2901                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2902                         /* Moves came from oldmoves or moves command
2903                            while we weren't doing anything else.
2904                            */
2905                         currentMove = forwardMostMove;
2906                         ClearHighlights();/*!!could figure this out*/
2907                         flipView = appData.flipView;
2908                         DrawPosition(TRUE, boards[currentMove]);
2909                         DisplayBothClocks();
2910                         sprintf(str, "%s vs. %s",
2911                                 gameInfo.white, gameInfo.black);
2912                         DisplayTitle(str);
2913                         gameMode = IcsIdle;
2914                     } else {
2915                         /* Moves were history of an active game */
2916                         if (gameInfo.resultDetails != NULL) {
2917                             free(gameInfo.resultDetails);
2918                             gameInfo.resultDetails = NULL;
2919                         }
2920                     }
2921                     HistorySet(parseList, backwardMostMove,
2922                                forwardMostMove, currentMove-1);
2923                     DisplayMove(currentMove - 1);
2924                     if (started == STARTED_MOVES) next_out = i;
2925                     started = STARTED_NONE;
2926                     ics_getting_history = H_FALSE;
2927                     break;
2928
2929                   case STARTED_OBSERVE:
2930                     started = STARTED_NONE;
2931                     SendToICS(ics_prefix);
2932                     SendToICS("refresh\n");
2933                     break;
2934
2935                   default:
2936                     break;
2937                 }
2938                 if(bookHit) { // [HGM] book: simulate book reply
2939                     static char bookMove[MSG_SIZ]; // a bit generous?
2940
2941                     programStats.nodes = programStats.depth = programStats.time = 
2942                     programStats.score = programStats.got_only_move = 0;
2943                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2944
2945                     strcpy(bookMove, "move ");
2946                     strcat(bookMove, bookHit);
2947                     HandleMachineMove(bookMove, &first);
2948                 }
2949                 continue;
2950             }
2951             
2952             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953                  started == STARTED_HOLDINGS ||
2954                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955                 /* Accumulate characters in move list or board */
2956                 parse[parse_pos++] = buf[i];
2957             }
2958             
2959             /* Start of game messages.  Mostly we detect start of game
2960                when the first board image arrives.  On some versions
2961                of the ICS, though, we need to do a "refresh" after starting
2962                to observe in order to get the current board right away. */
2963             if (looking_at(buf, &i, "Adding game * to observation list")) {
2964                 started = STARTED_OBSERVE;
2965                 continue;
2966             }
2967
2968             /* Handle auto-observe */
2969             if (appData.autoObserve &&
2970                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2972                 char *player;
2973                 /* Choose the player that was highlighted, if any. */
2974                 if (star_match[0][0] == '\033' ||
2975                     star_match[1][0] != '\033') {
2976                     player = star_match[0];
2977                 } else {
2978                     player = star_match[2];
2979                 }
2980                 sprintf(str, "%sobserve %s\n",
2981                         ics_prefix, StripHighlightAndTitle(player));
2982                 SendToICS(str);
2983
2984                 /* Save ratings from notify string */
2985                 strcpy(player1Name, star_match[0]);
2986                 player1Rating = string_to_rating(star_match[1]);
2987                 strcpy(player2Name, star_match[2]);
2988                 player2Rating = string_to_rating(star_match[3]);
2989
2990                 if (appData.debugMode)
2991                   fprintf(debugFP, 
2992                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2993                           player1Name, player1Rating,
2994                           player2Name, player2Rating);
2995
2996                 continue;
2997             }
2998
2999             /* Deal with automatic examine mode after a game,
3000                and with IcsObserving -> IcsExamining transition */
3001             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002                 looking_at(buf, &i, "has made you an examiner of game *")) {
3003
3004                 int gamenum = atoi(star_match[0]);
3005                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006                     gamenum == ics_gamenum) {
3007                     /* We were already playing or observing this game;
3008                        no need to refetch history */
3009                     gameMode = IcsExamining;
3010                     if (pausing) {
3011                         pauseExamForwardMostMove = forwardMostMove;
3012                     } else if (currentMove < forwardMostMove) {
3013                         ForwardInner(forwardMostMove);
3014                     }
3015                 } else {
3016                     /* I don't think this case really can happen */
3017                     SendToICS(ics_prefix);
3018                     SendToICS("refresh\n");
3019                 }
3020                 continue;
3021             }    
3022             
3023             /* Error messages */
3024 //          if (ics_user_moved) {
3025             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026                 if (looking_at(buf, &i, "Illegal move") ||
3027                     looking_at(buf, &i, "Not a legal move") ||
3028                     looking_at(buf, &i, "Your king is in check") ||
3029                     looking_at(buf, &i, "It isn't your turn") ||
3030                     looking_at(buf, &i, "It is not your move")) {
3031                     /* Illegal move */
3032                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033                         currentMove = --forwardMostMove;
3034                         DisplayMove(currentMove - 1); /* before DMError */
3035                         DrawPosition(FALSE, boards[currentMove]);
3036                         SwitchClocks();
3037                         DisplayBothClocks();
3038                     }
3039                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3040                     ics_user_moved = 0;
3041                     continue;
3042                 }
3043             }
3044
3045             if (looking_at(buf, &i, "still have time") ||
3046                 looking_at(buf, &i, "not out of time") ||
3047                 looking_at(buf, &i, "either player is out of time") ||
3048                 looking_at(buf, &i, "has timeseal; checking")) {
3049                 /* We must have called his flag a little too soon */
3050                 whiteFlag = blackFlag = FALSE;
3051                 continue;
3052             }
3053
3054             if (looking_at(buf, &i, "added * seconds to") ||
3055                 looking_at(buf, &i, "seconds were added to")) {
3056                 /* Update the clocks */
3057                 SendToICS(ics_prefix);
3058                 SendToICS("refresh\n");
3059                 continue;
3060             }
3061
3062             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063                 ics_clock_paused = TRUE;
3064                 StopClocks();
3065                 continue;
3066             }
3067
3068             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069                 ics_clock_paused = FALSE;
3070                 StartClocks();
3071                 continue;
3072             }
3073
3074             /* Grab player ratings from the Creating: message.
3075                Note we have to check for the special case when
3076                the ICS inserts things like [white] or [black]. */
3077             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3079                 /* star_matches:
3080                    0    player 1 name (not necessarily white)
3081                    1    player 1 rating
3082                    2    empty, white, or black (IGNORED)
3083                    3    player 2 name (not necessarily black)
3084                    4    player 2 rating
3085                    
3086                    The names/ratings are sorted out when the game
3087                    actually starts (below).
3088                 */
3089                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090                 player1Rating = string_to_rating(star_match[1]);
3091                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092                 player2Rating = string_to_rating(star_match[4]);
3093
3094                 if (appData.debugMode)
3095                   fprintf(debugFP, 
3096                           "Ratings from 'Creating:' %s %d, %s %d\n",
3097                           player1Name, player1Rating,
3098                           player2Name, player2Rating);
3099
3100                 continue;
3101             }
3102             
3103             /* Improved generic start/end-of-game messages */
3104             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106                 /* If tkind == 0: */
3107                 /* star_match[0] is the game number */
3108                 /*           [1] is the white player's name */
3109                 /*           [2] is the black player's name */
3110                 /* For end-of-game: */
3111                 /*           [3] is the reason for the game end */
3112                 /*           [4] is a PGN end game-token, preceded by " " */
3113                 /* For start-of-game: */
3114                 /*           [3] begins with "Creating" or "Continuing" */
3115                 /*           [4] is " *" or empty (don't care). */
3116                 int gamenum = atoi(star_match[0]);
3117                 char *whitename, *blackname, *why, *endtoken;
3118                 ChessMove endtype = (ChessMove) 0;
3119
3120                 if (tkind == 0) {
3121                   whitename = star_match[1];
3122                   blackname = star_match[2];
3123                   why = star_match[3];
3124                   endtoken = star_match[4];
3125                 } else {
3126                   whitename = star_match[1];
3127                   blackname = star_match[3];
3128                   why = star_match[5];
3129                   endtoken = star_match[6];
3130                 }
3131
3132                 /* Game start messages */
3133                 if (strncmp(why, "Creating ", 9) == 0 ||
3134                     strncmp(why, "Continuing ", 11) == 0) {
3135                     gs_gamenum = gamenum;
3136                     strcpy(gs_kind, strchr(why, ' ') + 1);
3137 #if ZIPPY
3138                     if (appData.zippyPlay) {
3139                         ZippyGameStart(whitename, blackname);
3140                     }
3141 #endif /*ZIPPY*/
3142                     continue;
3143                 }
3144
3145                 /* Game end messages */
3146                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3147                     ics_gamenum != gamenum) {
3148                     continue;
3149                 }
3150                 while (endtoken[0] == ' ') endtoken++;
3151                 switch (endtoken[0]) {
3152                   case '*':
3153                   default:
3154                     endtype = GameUnfinished;
3155                     break;
3156                   case '0':
3157                     endtype = BlackWins;
3158                     break;
3159                   case '1':
3160                     if (endtoken[1] == '/')
3161                       endtype = GameIsDrawn;
3162                     else
3163                       endtype = WhiteWins;
3164                     break;
3165                 }
3166                 GameEnds(endtype, why, GE_ICS);
3167 #if ZIPPY
3168                 if (appData.zippyPlay && first.initDone) {
3169                     ZippyGameEnd(endtype, why);
3170                     if (first.pr == NULL) {
3171                       /* Start the next process early so that we'll
3172                          be ready for the next challenge */
3173                       StartChessProgram(&first);
3174                     }
3175                     /* Send "new" early, in case this command takes
3176                        a long time to finish, so that we'll be ready
3177                        for the next challenge. */
3178                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3179                     Reset(TRUE, TRUE);
3180                 }
3181 #endif /*ZIPPY*/
3182                 continue;
3183             }
3184
3185             if (looking_at(buf, &i, "Removing game * from observation") ||
3186                 looking_at(buf, &i, "no longer observing game *") ||
3187                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3188                 if (gameMode == IcsObserving &&
3189                     atoi(star_match[0]) == ics_gamenum)
3190                   {
3191                       /* icsEngineAnalyze */
3192                       if (appData.icsEngineAnalyze) {
3193                             ExitAnalyzeMode();
3194                             ModeHighlight();
3195                       }
3196                       StopClocks();
3197                       gameMode = IcsIdle;
3198                       ics_gamenum = -1;
3199                       ics_user_moved = FALSE;
3200                   }
3201                 continue;
3202             }
3203
3204             if (looking_at(buf, &i, "no longer examining game *")) {
3205                 if (gameMode == IcsExamining &&
3206                     atoi(star_match[0]) == ics_gamenum)
3207                   {
3208                       gameMode = IcsIdle;
3209                       ics_gamenum = -1;
3210                       ics_user_moved = FALSE;
3211                   }
3212                 continue;
3213             }
3214
3215             /* Advance leftover_start past any newlines we find,
3216                so only partial lines can get reparsed */
3217             if (looking_at(buf, &i, "\n")) {
3218                 prevColor = curColor;
3219                 if (curColor != ColorNormal) {
3220                     if (oldi > next_out) {
3221                         SendToPlayer(&buf[next_out], oldi - next_out);
3222                         next_out = oldi;
3223                     }
3224                     Colorize(ColorNormal, FALSE);
3225                     curColor = ColorNormal;
3226                 }
3227                 if (started == STARTED_BOARD) {
3228                     started = STARTED_NONE;
3229                     parse[parse_pos] = NULLCHAR;
3230                     ParseBoard12(parse);
3231                     ics_user_moved = 0;
3232
3233                     /* Send premove here */
3234                     if (appData.premove) {
3235                       char str[MSG_SIZ];
3236                       if (currentMove == 0 &&
3237                           gameMode == IcsPlayingWhite &&
3238                           appData.premoveWhite) {
3239                         sprintf(str, "%s\n", appData.premoveWhiteText);
3240                         if (appData.debugMode)
3241                           fprintf(debugFP, "Sending premove:\n");
3242                         SendToICS(str);
3243                       } else if (currentMove == 1 &&
3244                                  gameMode == IcsPlayingBlack &&
3245                                  appData.premoveBlack) {
3246                         sprintf(str, "%s\n", appData.premoveBlackText);
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                         SendToICS(str);
3250                       } else if (gotPremove) {
3251                         gotPremove = 0;
3252                         ClearPremoveHighlights();
3253                         if (appData.debugMode)
3254                           fprintf(debugFP, "Sending premove:\n");
3255                           UserMoveEvent(premoveFromX, premoveFromY, 
3256                                         premoveToX, premoveToY, 
3257                                         premovePromoChar);
3258                       }
3259                     }
3260
3261                     /* Usually suppress following prompt */
3262                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263                         if (looking_at(buf, &i, "*% ")) {
3264                             savingComment = FALSE;
3265                         }
3266                     }
3267                     next_out = i;
3268                 } else if (started == STARTED_HOLDINGS) {
3269                     int gamenum;
3270                     char new_piece[MSG_SIZ];
3271                     started = STARTED_NONE;
3272                     parse[parse_pos] = NULLCHAR;
3273                     if (appData.debugMode)
3274                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275                                                         parse, currentMove);
3276                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277                         gamenum == ics_gamenum) {
3278                         if (gameInfo.variant == VariantNormal) {
3279                           /* [HGM] We seem to switch variant during a game!
3280                            * Presumably no holdings were displayed, so we have
3281                            * to move the position two files to the right to
3282                            * create room for them!
3283                            */
3284                           VariantClass newVariant;
3285                           switch(gameInfo.boardWidth) { // base guess on board width
3286                                 case 9:  newVariant = VariantShogi; break;
3287                                 case 10: newVariant = VariantGreat; break;
3288                                 default: newVariant = VariantCrazyhouse; break;
3289                           }
3290                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3291                           /* Get a move list just to see the header, which
3292                              will tell us whether this is really bug or zh */
3293                           if (ics_getting_history == H_FALSE) {
3294                             ics_getting_history = H_REQUESTED;
3295                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3296                             SendToICS(str);
3297                           }
3298                         }
3299                         new_piece[0] = NULLCHAR;
3300                         sscanf(parse, "game %d white [%s black [%s <- %s",
3301                                &gamenum, white_holding, black_holding,
3302                                new_piece);
3303                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3304                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3305                         /* [HGM] copy holdings to board holdings area */
3306                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3307                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3308                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3309 #if ZIPPY
3310                         if (appData.zippyPlay && first.initDone) {
3311                             ZippyHoldings(white_holding, black_holding,
3312                                           new_piece);
3313                         }
3314 #endif /*ZIPPY*/
3315                         if (tinyLayout || smallLayout) {
3316                             char wh[16], bh[16];
3317                             PackHolding(wh, white_holding);
3318                             PackHolding(bh, black_holding);
3319                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3320                                     gameInfo.white, gameInfo.black);
3321                         } else {
3322                             sprintf(str, "%s [%s] vs. %s [%s]",
3323                                     gameInfo.white, white_holding,
3324                                     gameInfo.black, black_holding);
3325                         }
3326
3327                         DrawPosition(FALSE, boards[currentMove]);
3328                         DisplayTitle(str);
3329                     }
3330                     /* Suppress following prompt */
3331                     if (looking_at(buf, &i, "*% ")) {
3332                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3333                         savingComment = FALSE;
3334                     }
3335                     next_out = i;
3336                 }
3337                 continue;
3338             }
3339
3340             i++;                /* skip unparsed character and loop back */
3341         }
3342         
3343         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3344             started != STARTED_HOLDINGS && i > next_out) {
3345             SendToPlayer(&buf[next_out], i - next_out);
3346             next_out = i;
3347         }
3348         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3349         
3350         leftover_len = buf_len - leftover_start;
3351         /* if buffer ends with something we couldn't parse,
3352            reparse it after appending the next read */
3353         
3354     } else if (count == 0) {
3355         RemoveInputSource(isr);
3356         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3357     } else {
3358         DisplayFatalError(_("Error reading from ICS"), error, 1);
3359     }
3360 }
3361
3362
3363 /* Board style 12 looks like this:
3364    
3365    <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
3366    
3367  * The "<12> " is stripped before it gets to this routine.  The two
3368  * trailing 0's (flip state and clock ticking) are later addition, and
3369  * some chess servers may not have them, or may have only the first.
3370  * Additional trailing fields may be added in the future.  
3371  */
3372
3373 #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"
3374
3375 #define RELATION_OBSERVING_PLAYED    0
3376 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3377 #define RELATION_PLAYING_MYMOVE      1
3378 #define RELATION_PLAYING_NOTMYMOVE  -1
3379 #define RELATION_EXAMINING           2
3380 #define RELATION_ISOLATED_BOARD     -3
3381 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3382
3383 void
3384 ParseBoard12(string)
3385      char *string;
3386
3387     GameMode newGameMode;
3388     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3389     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3390     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3391     char to_play, board_chars[200];
3392     char move_str[500], str[500], elapsed_time[500];
3393     char black[32], white[32];
3394     Board board;
3395     int prevMove = currentMove;
3396     int ticking = 2;
3397     ChessMove moveType;
3398     int fromX, fromY, toX, toY;
3399     char promoChar;
3400     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3401     char *bookHit = NULL; // [HGM] book
3402     Boolean weird = FALSE, reqFlag = FALSE;
3403
3404     fromX = fromY = toX = toY = -1;
3405     
3406     newGame = FALSE;
3407
3408     if (appData.debugMode)
3409       fprintf(debugFP, _("Parsing board: %s\n"), string);
3410
3411     move_str[0] = NULLCHAR;
3412     elapsed_time[0] = NULLCHAR;
3413     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3414         int  i = 0, j;
3415         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3416             if(string[i] == ' ') { ranks++; files = 0; }
3417             else files++;
3418             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3419             i++;
3420         }
3421         for(j = 0; j <i; j++) board_chars[j] = string[j];
3422         board_chars[i] = '\0';
3423         string += i + 1;
3424     }
3425     n = sscanf(string, PATTERN, &to_play, &double_push,
3426                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3427                &gamenum, white, black, &relation, &basetime, &increment,
3428                &white_stren, &black_stren, &white_time, &black_time,
3429                &moveNum, str, elapsed_time, move_str, &ics_flip,
3430                &ticking);
3431
3432     if (n < 21) {
3433         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3434         DisplayError(str, 0);
3435         return;
3436     }
3437
3438     /* Convert the move number to internal form */
3439     moveNum = (moveNum - 1) * 2;
3440     if (to_play == 'B') moveNum++;
3441     if (moveNum >= MAX_MOVES) {
3442       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3443                         0, 1);
3444       return;
3445     }
3446     
3447     switch (relation) {
3448       case RELATION_OBSERVING_PLAYED:
3449       case RELATION_OBSERVING_STATIC:
3450         if (gamenum == -1) {
3451             /* Old ICC buglet */
3452             relation = RELATION_OBSERVING_STATIC;
3453         }
3454         newGameMode = IcsObserving;
3455         break;
3456       case RELATION_PLAYING_MYMOVE:
3457       case RELATION_PLAYING_NOTMYMOVE:
3458         newGameMode =
3459           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3460             IcsPlayingWhite : IcsPlayingBlack;
3461         break;
3462       case RELATION_EXAMINING:
3463         newGameMode = IcsExamining;
3464         break;
3465       case RELATION_ISOLATED_BOARD:
3466       default:
3467         /* Just display this board.  If user was doing something else,
3468            we will forget about it until the next board comes. */ 
3469         newGameMode = IcsIdle;
3470         break;
3471       case RELATION_STARTING_POSITION:
3472         newGameMode = gameMode;
3473         break;
3474     }
3475     
3476     /* Modify behavior for initial board display on move listing
3477        of wild games.
3478        */
3479     switch (ics_getting_history) {
3480       case H_FALSE:
3481       case H_REQUESTED:
3482         break;
3483       case H_GOT_REQ_HEADER:
3484       case H_GOT_UNREQ_HEADER:
3485         /* This is the initial position of the current game */
3486         gamenum = ics_gamenum;
3487         moveNum = 0;            /* old ICS bug workaround */
3488         if (to_play == 'B') {
3489           startedFromSetupPosition = TRUE;
3490           blackPlaysFirst = TRUE;
3491           moveNum = 1;
3492           if (forwardMostMove == 0) forwardMostMove = 1;
3493           if (backwardMostMove == 0) backwardMostMove = 1;
3494           if (currentMove == 0) currentMove = 1;
3495         }
3496         newGameMode = gameMode;
3497         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3498         break;
3499       case H_GOT_UNWANTED_HEADER:
3500         /* This is an initial board that we don't want */
3501         return;
3502       case H_GETTING_MOVES:
3503         /* Should not happen */
3504         DisplayError(_("Error gathering move list: extra board"), 0);
3505         ics_getting_history = H_FALSE;
3506         return;
3507     }
3508
3509    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3510                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3511      /* [HGM] We seem to have switched variant unexpectedly
3512       * Try to guess new variant from board size
3513       */
3514           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3515           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3516           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3517           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3518           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3519           if(!weird) newVariant = VariantNormal;
3520           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3521           /* Get a move list just to see the header, which
3522              will tell us whether this is really bug or zh */
3523           if (ics_getting_history == H_FALSE) {
3524             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3525             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3526             SendToICS(str);
3527           }
3528     }
3529     
3530     /* Take action if this is the first board of a new game, or of a
3531        different game than is currently being displayed.  */
3532     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3533         relation == RELATION_ISOLATED_BOARD) {
3534         
3535         /* Forget the old game and get the history (if any) of the new one */
3536         if (gameMode != BeginningOfGame) {
3537           Reset(TRUE, TRUE);
3538         }
3539         newGame = TRUE;
3540         if (appData.autoRaiseBoard) BoardToTop();
3541         prevMove = -3;
3542         if (gamenum == -1) {
3543             newGameMode = IcsIdle;
3544         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3545                    appData.getMoveList && !reqFlag) {
3546             /* Need to get game history */
3547             ics_getting_history = H_REQUESTED;
3548             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3549             SendToICS(str);
3550         }
3551         
3552         /* Initially flip the board to have black on the bottom if playing
3553            black or if the ICS flip flag is set, but let the user change
3554            it with the Flip View button. */
3555         flipView = appData.autoFlipView ? 
3556           (newGameMode == IcsPlayingBlack) || ics_flip :
3557           appData.flipView;
3558         
3559         /* Done with values from previous mode; copy in new ones */
3560         gameMode = newGameMode;
3561         ModeHighlight();
3562         ics_gamenum = gamenum;
3563         if (gamenum == gs_gamenum) {
3564             int klen = strlen(gs_kind);
3565             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3566             sprintf(str, "ICS %s", gs_kind);
3567             gameInfo.event = StrSave(str);
3568         } else {
3569             gameInfo.event = StrSave("ICS game");
3570         }
3571         gameInfo.site = StrSave(appData.icsHost);
3572         gameInfo.date = PGNDate();
3573         gameInfo.round = StrSave("-");
3574         gameInfo.white = StrSave(white);
3575         gameInfo.black = StrSave(black);
3576         timeControl = basetime * 60 * 1000;
3577         timeControl_2 = 0;
3578         timeIncrement = increment * 1000;
3579         movesPerSession = 0;
3580         gameInfo.timeControl = TimeControlTagValue();
3581         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3582   if (appData.debugMode) {
3583     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3584     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3585     setbuf(debugFP, NULL);
3586   }
3587
3588         gameInfo.outOfBook = NULL;
3589         
3590         /* Do we have the ratings? */
3591         if (strcmp(player1Name, white) == 0 &&
3592             strcmp(player2Name, black) == 0) {
3593             if (appData.debugMode)
3594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3595                       player1Rating, player2Rating);
3596             gameInfo.whiteRating = player1Rating;
3597             gameInfo.blackRating = player2Rating;
3598         } else if (strcmp(player2Name, white) == 0 &&
3599                    strcmp(player1Name, black) == 0) {
3600             if (appData.debugMode)
3601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3602                       player2Rating, player1Rating);
3603             gameInfo.whiteRating = player2Rating;
3604             gameInfo.blackRating = player1Rating;
3605         }
3606         player1Name[0] = player2Name[0] = NULLCHAR;
3607
3608         /* Silence shouts if requested */
3609         if (appData.quietPlay &&
3610             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3611             SendToICS(ics_prefix);
3612             SendToICS("set shout 0\n");
3613         }
3614     }
3615     
3616     /* Deal with midgame name changes */
3617     if (!newGame) {
3618         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3619             if (gameInfo.white) free(gameInfo.white);
3620             gameInfo.white = StrSave(white);
3621         }
3622         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3623             if (gameInfo.black) free(gameInfo.black);
3624             gameInfo.black = StrSave(black);
3625         }
3626     }
3627     
3628     /* Throw away game result if anything actually changes in examine mode */
3629     if (gameMode == IcsExamining && !newGame) {
3630         gameInfo.result = GameUnfinished;
3631         if (gameInfo.resultDetails != NULL) {
3632             free(gameInfo.resultDetails);
3633             gameInfo.resultDetails = NULL;
3634         }
3635     }
3636     
3637     /* In pausing && IcsExamining mode, we ignore boards coming
3638        in if they are in a different variation than we are. */
3639     if (pauseExamInvalid) return;
3640     if (pausing && gameMode == IcsExamining) {
3641         if (moveNum <= pauseExamForwardMostMove) {
3642             pauseExamInvalid = TRUE;
3643             forwardMostMove = pauseExamForwardMostMove;
3644             return;
3645         }
3646     }
3647     
3648   if (appData.debugMode) {
3649     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3650   }
3651     /* Parse the board */
3652     for (k = 0; k < ranks; k++) {
3653       for (j = 0; j < files; j++)
3654         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3655       if(gameInfo.holdingsWidth > 1) {
3656            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3657            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3658       }
3659     }
3660     CopyBoard(boards[moveNum], board);
3661     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3662     if (moveNum == 0) {
3663         startedFromSetupPosition =
3664           !CompareBoards(board, initialPosition);
3665         if(startedFromSetupPosition)
3666             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3667     }
3668
3669     /* [HGM] Set castling rights. Take the outermost Rooks,
3670        to make it also work for FRC opening positions. Note that board12
3671        is really defective for later FRC positions, as it has no way to
3672        indicate which Rook can castle if they are on the same side of King.
3673        For the initial position we grant rights to the outermost Rooks,
3674        and remember thos rights, and we then copy them on positions
3675        later in an FRC game. This means WB might not recognize castlings with
3676        Rooks that have moved back to their original position as illegal,
3677        but in ICS mode that is not its job anyway.
3678     */
3679     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3680     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3681
3682         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3683             if(board[0][i] == WhiteRook) j = i;
3684         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3686             if(board[0][i] == WhiteRook) j = i;
3687         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3692             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3693         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3694
3695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3697             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699             if(board[BOARD_HEIGHT-1][k] == bKing)
3700                 initialRights[5] = castlingRights[moveNum][5] = k;
3701     } else { int r;
3702         r = castlingRights[moveNum][0] = initialRights[0];
3703         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3704         r = castlingRights[moveNum][1] = initialRights[1];
3705         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3706         r = castlingRights[moveNum][3] = initialRights[3];
3707         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3708         r = castlingRights[moveNum][4] = initialRights[4];
3709         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3710         /* wildcastle kludge: always assume King has rights */
3711         r = castlingRights[moveNum][2] = initialRights[2];
3712         r = castlingRights[moveNum][5] = initialRights[5];
3713     }
3714     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3715     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3716
3717     
3718     if (ics_getting_history == H_GOT_REQ_HEADER ||
3719         ics_getting_history == H_GOT_UNREQ_HEADER) {
3720         /* This was an initial position from a move list, not
3721            the current position */
3722         return;
3723     }
3724     
3725     /* Update currentMove and known move number limits */
3726     newMove = newGame || moveNum > forwardMostMove;
3727
3728     if (newGame) {
3729         forwardMostMove = backwardMostMove = currentMove = moveNum;
3730         if (gameMode == IcsExamining && moveNum == 0) {
3731           /* Workaround for ICS limitation: we are not told the wild
3732              type when starting to examine a game.  But if we ask for
3733              the move list, the move list header will tell us */
3734             ics_getting_history = H_REQUESTED;
3735             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3736             SendToICS(str);
3737         }
3738     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3739                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3740 #if ZIPPY
3741         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3742         /* [HGM] applied this also to an engine that is silently watching        */
3743         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3744             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3745             gameInfo.variant == currentlyInitializedVariant) {
3746           takeback = forwardMostMove - moveNum;
3747           for (i = 0; i < takeback; i++) {
3748             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3749             SendToProgram("undo\n", &first);
3750           }
3751         }
3752 #endif
3753
3754         forwardMostMove = moveNum;
3755         if (!pausing || currentMove > forwardMostMove)
3756           currentMove = forwardMostMove;
3757     } else {
3758         /* New part of history that is not contiguous with old part */ 
3759         if (pausing && gameMode == IcsExamining) {
3760             pauseExamInvalid = TRUE;
3761             forwardMostMove = pauseExamForwardMostMove;
3762             return;
3763         }
3764         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3765 #if ZIPPY
3766             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3767                 // [HGM] when we will receive the move list we now request, it will be
3768                 // fed to the engine from the first move on. So if the engine is not
3769                 // in the initial position now, bring it there.
3770                 InitChessProgram(&first, 0);
3771             }
3772 #endif
3773             ics_getting_history = H_REQUESTED;
3774             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3775             SendToICS(str);
3776         }
3777         forwardMostMove = backwardMostMove = currentMove = moveNum;
3778     }
3779     
3780     /* Update the clocks */
3781     if (strchr(elapsed_time, '.')) {
3782       /* Time is in ms */
3783       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3784       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3785     } else {
3786       /* Time is in seconds */
3787       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3788       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3789     }
3790       
3791
3792 #if ZIPPY
3793     if (appData.zippyPlay && newGame &&
3794         gameMode != IcsObserving && gameMode != IcsIdle &&
3795         gameMode != IcsExamining)
3796       ZippyFirstBoard(moveNum, basetime, increment);
3797 #endif
3798     
3799     /* Put the move on the move list, first converting
3800        to canonical algebraic form. */
3801     if (moveNum > 0) {
3802   if (appData.debugMode) {
3803     if (appData.debugMode) { int f = forwardMostMove;
3804         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3805                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3806     }
3807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808     fprintf(debugFP, "moveNum = %d\n", moveNum);
3809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810     setbuf(debugFP, NULL);
3811   }
3812         if (moveNum <= backwardMostMove) {
3813             /* We don't know what the board looked like before
3814                this move.  Punt. */
3815             strcpy(parseList[moveNum - 1], move_str);
3816             strcat(parseList[moveNum - 1], " ");
3817             strcat(parseList[moveNum - 1], elapsed_time);
3818             moveList[moveNum - 1][0] = NULLCHAR;
3819         } else if (strcmp(move_str, "none") == 0) {
3820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821             /* Again, we don't know what the board looked like;
3822                this is really the start of the game. */
3823             parseList[moveNum - 1][0] = NULLCHAR;
3824             moveList[moveNum - 1][0] = NULLCHAR;
3825             backwardMostMove = moveNum;
3826             startedFromSetupPosition = TRUE;
3827             fromX = fromY = toX = toY = -1;
3828         } else {
3829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3830           //                 So we parse the long-algebraic move string in stead of the SAN move
3831           int valid; char buf[MSG_SIZ], *prom;
3832
3833           // str looks something like "Q/a1-a2"; kill the slash
3834           if(str[1] == '/') 
3835                 sprintf(buf, "%c%s", str[0], str+2);
3836           else  strcpy(buf, str); // might be castling
3837           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3838                 strcat(buf, prom); // long move lacks promo specification!
3839           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840                 if(appData.debugMode) 
3841                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842                 strcpy(move_str, buf);
3843           }
3844           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845                                 &fromX, &fromY, &toX, &toY, &promoChar)
3846                || ParseOneMove(buf, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar);
3848           // end of long SAN patch
3849           if (valid) {
3850             (void) CoordsToAlgebraic(boards[moveNum - 1],
3851                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3852                                      fromY, fromX, toY, toX, promoChar,
3853                                      parseList[moveNum-1]);
3854             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3855                              castlingRights[moveNum]) ) {
3856               case MT_NONE:
3857               case MT_STALEMATE:
3858               default:
3859                 break;
3860               case MT_CHECK:
3861                 if(gameInfo.variant != VariantShogi)
3862                     strcat(parseList[moveNum - 1], "+");
3863                 break;
3864               case MT_CHECKMATE:
3865               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3866                 strcat(parseList[moveNum - 1], "#");
3867                 break;
3868             }
3869             strcat(parseList[moveNum - 1], " ");
3870             strcat(parseList[moveNum - 1], elapsed_time);
3871             /* currentMoveString is set as a side-effect of ParseOneMove */
3872             strcpy(moveList[moveNum - 1], currentMoveString);
3873             strcat(moveList[moveNum - 1], "\n");
3874           } else {
3875             /* Move from ICS was illegal!?  Punt. */
3876   if (appData.debugMode) {
3877     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3878     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3879   }
3880             strcpy(parseList[moveNum - 1], move_str);
3881             strcat(parseList[moveNum - 1], " ");
3882             strcat(parseList[moveNum - 1], elapsed_time);
3883             moveList[moveNum - 1][0] = NULLCHAR;
3884             fromX = fromY = toX = toY = -1;
3885           }
3886         }
3887   if (appData.debugMode) {
3888     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3889     setbuf(debugFP, NULL);
3890   }
3891
3892 #if ZIPPY
3893         /* Send move to chess program (BEFORE animating it). */
3894         if (appData.zippyPlay && !newGame && newMove && 
3895            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3896
3897             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3898                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3899                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3900                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3901                             move_str);
3902                     DisplayError(str, 0);
3903                 } else {
3904                     if (first.sendTime) {
3905                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3906                     }
3907                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3908                     if (firstMove && !bookHit) {
3909                         firstMove = FALSE;
3910                         if (first.useColors) {
3911                           SendToProgram(gameMode == IcsPlayingWhite ?
3912                                         "white\ngo\n" :
3913                                         "black\ngo\n", &first);
3914                         } else {
3915                           SendToProgram("go\n", &first);
3916                         }
3917                         first.maybeThinking = TRUE;
3918                     }
3919                 }
3920             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3921               if (moveList[moveNum - 1][0] == NULLCHAR) {
3922                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3923                 DisplayError(str, 0);
3924               } else {
3925                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3926                 SendMoveToProgram(moveNum - 1, &first);
3927               }
3928             }
3929         }
3930 #endif
3931     }
3932
3933     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3934         /* If move comes from a remote source, animate it.  If it
3935            isn't remote, it will have already been animated. */
3936         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3937             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3938         }
3939         if (!pausing && appData.highlightLastMove) {
3940             SetHighlights(fromX, fromY, toX, toY);
3941         }
3942     }
3943     
3944     /* Start the clocks */
3945     whiteFlag = blackFlag = FALSE;
3946     appData.clockMode = !(basetime == 0 && increment == 0);
3947     if (ticking == 0) {
3948       ics_clock_paused = TRUE;
3949       StopClocks();
3950     } else if (ticking == 1) {
3951       ics_clock_paused = FALSE;
3952     }
3953     if (gameMode == IcsIdle ||
3954         relation == RELATION_OBSERVING_STATIC ||
3955         relation == RELATION_EXAMINING ||
3956         ics_clock_paused)
3957       DisplayBothClocks();
3958     else
3959       StartClocks();
3960     
3961     /* Display opponents and material strengths */
3962     if (gameInfo.variant != VariantBughouse &&
3963         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3964         if (tinyLayout || smallLayout) {
3965             if(gameInfo.variant == VariantNormal)
3966                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3967                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3968                     basetime, increment);
3969             else
3970                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3971                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3972                     basetime, increment, (int) gameInfo.variant);
3973         } else {
3974             if(gameInfo.variant == VariantNormal)
3975                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3976                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3977                     basetime, increment);
3978             else
3979                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment, VariantName(gameInfo.variant));
3982         }
3983         DisplayTitle(str);
3984   if (appData.debugMode) {
3985     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3986   }
3987     }
3988
3989    
3990     /* Display the board */
3991     if (!pausing && !appData.noGUI) {
3992       
3993       if (appData.premove)
3994           if (!gotPremove || 
3995              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3996              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3997               ClearPremoveHighlights();
3998
3999       DrawPosition(FALSE, boards[currentMove]);
4000       DisplayMove(moveNum - 1);
4001       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4002             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4003               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4004         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4005       }
4006     }
4007
4008     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4009 #if ZIPPY
4010     if(bookHit) { // [HGM] book: simulate book reply
4011         static char bookMove[MSG_SIZ]; // a bit generous?
4012
4013         programStats.nodes = programStats.depth = programStats.time = 
4014         programStats.score = programStats.got_only_move = 0;
4015         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4016
4017         strcpy(bookMove, "move ");
4018         strcat(bookMove, bookHit);
4019         HandleMachineMove(bookMove, &first);
4020     }
4021 #endif
4022 }
4023
4024 void
4025 GetMoveListEvent()
4026 {
4027     char buf[MSG_SIZ];
4028     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4029         ics_getting_history = H_REQUESTED;
4030         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4031         SendToICS(buf);
4032     }
4033 }
4034
4035 void
4036 AnalysisPeriodicEvent(force)
4037      int force;
4038 {
4039     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4040          && !force) || !appData.periodicUpdates)
4041       return;
4042
4043     /* Send . command to Crafty to collect stats */
4044     SendToProgram(".\n", &first);
4045
4046     /* Don't send another until we get a response (this makes
4047        us stop sending to old Crafty's which don't understand
4048        the "." command (sending illegal cmds resets node count & time,
4049        which looks bad)) */
4050     programStats.ok_to_send = 0;
4051 }
4052
4053 void ics_update_width(new_width)
4054         int new_width;
4055 {
4056         ics_printf("set width %d\n", new_width);
4057 }
4058
4059 void
4060 SendMoveToProgram(moveNum, cps)
4061      int moveNum;
4062      ChessProgramState *cps;
4063 {
4064     char buf[MSG_SIZ];
4065
4066     if (cps->useUsermove) {
4067       SendToProgram("usermove ", cps);
4068     }
4069     if (cps->useSAN) {
4070       char *space;
4071       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4072         int len = space - parseList[moveNum];
4073         memcpy(buf, parseList[moveNum], len);
4074         buf[len++] = '\n';
4075         buf[len] = NULLCHAR;
4076       } else {
4077         sprintf(buf, "%s\n", parseList[moveNum]);
4078       }
4079       SendToProgram(buf, cps);
4080     } else {
4081       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4082         AlphaRank(moveList[moveNum], 4);
4083         SendToProgram(moveList[moveNum], cps);
4084         AlphaRank(moveList[moveNum], 4); // and back
4085       } else
4086       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4087        * the engine. It would be nice to have a better way to identify castle 
4088        * moves here. */
4089       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4090                                                                          && cps->useOOCastle) {
4091         int fromX = moveList[moveNum][0] - AAA; 
4092         int fromY = moveList[moveNum][1] - ONE;
4093         int toX = moveList[moveNum][2] - AAA; 
4094         int toY = moveList[moveNum][3] - ONE;
4095         if((boards[moveNum][fromY][fromX] == WhiteKing 
4096             && boards[moveNum][toY][toX] == WhiteRook)
4097            || (boards[moveNum][fromY][fromX] == BlackKing 
4098                && boards[moveNum][toY][toX] == BlackRook)) {
4099           if(toX > fromX) SendToProgram("O-O\n", cps);
4100           else SendToProgram("O-O-O\n", cps);
4101         }
4102         else SendToProgram(moveList[moveNum], cps);
4103       }
4104       else SendToProgram(moveList[moveNum], cps);
4105       /* End of additions by Tord */
4106     }
4107
4108     /* [HGM] setting up the opening has brought engine in force mode! */
4109     /*       Send 'go' if we are in a mode where machine should play. */
4110     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4111         (gameMode == TwoMachinesPlay   ||
4112 #ifdef ZIPPY
4113          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4114 #endif
4115          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4116         SendToProgram("go\n", cps);
4117   if (appData.debugMode) {
4118     fprintf(debugFP, "(extra)\n");
4119   }
4120     }
4121     setboardSpoiledMachineBlack = 0;
4122 }
4123
4124 void
4125 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4126      ChessMove moveType;
4127      int fromX, fromY, toX, toY;
4128 {
4129     char user_move[MSG_SIZ];
4130
4131     switch (moveType) {
4132       default:
4133         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4134                 (int)moveType, fromX, fromY, toX, toY);
4135         DisplayError(user_move + strlen("say "), 0);
4136         break;
4137       case WhiteKingSideCastle:
4138       case BlackKingSideCastle:
4139       case WhiteQueenSideCastleWild:
4140       case BlackQueenSideCastleWild:
4141       /* PUSH Fabien */
4142       case WhiteHSideCastleFR:
4143       case BlackHSideCastleFR:
4144       /* POP Fabien */
4145         sprintf(user_move, "o-o\n");
4146         break;
4147       case WhiteQueenSideCastle:
4148       case BlackQueenSideCastle:
4149       case WhiteKingSideCastleWild:
4150       case BlackKingSideCastleWild:
4151       /* PUSH Fabien */
4152       case WhiteASideCastleFR:
4153       case BlackASideCastleFR:
4154       /* POP Fabien */
4155         sprintf(user_move, "o-o-o\n");
4156         break;
4157       case WhitePromotionQueen:
4158       case BlackPromotionQueen:
4159       case WhitePromotionRook:
4160       case BlackPromotionRook:
4161       case WhitePromotionBishop:
4162       case BlackPromotionBishop:
4163       case WhitePromotionKnight:
4164       case BlackPromotionKnight:
4165       case WhitePromotionKing:
4166       case BlackPromotionKing:
4167       case WhitePromotionChancellor:
4168       case BlackPromotionChancellor:
4169       case WhitePromotionArchbishop:
4170       case BlackPromotionArchbishop:
4171         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4172             sprintf(user_move, "%c%c%c%c=%c\n",
4173                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4174                 PieceToChar(WhiteFerz));
4175         else if(gameInfo.variant == VariantGreat)
4176             sprintf(user_move, "%c%c%c%c=%c\n",
4177                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4178                 PieceToChar(WhiteMan));
4179         else
4180             sprintf(user_move, "%c%c%c%c=%c\n",
4181                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4182                 PieceToChar(PromoPiece(moveType)));
4183         break;
4184       case WhiteDrop:
4185       case BlackDrop:
4186         sprintf(user_move, "%c@%c%c\n",
4187                 ToUpper(PieceToChar((ChessSquare) fromX)),
4188                 AAA + toX, ONE + toY);
4189         break;
4190       case NormalMove:
4191       case WhiteCapturesEnPassant:
4192       case BlackCapturesEnPassant:
4193       case IllegalMove:  /* could be a variant we don't quite understand */
4194         sprintf(user_move, "%c%c%c%c\n",
4195                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4196         break;
4197     }
4198     SendToICS(user_move);
4199     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4200         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4201 }
4202
4203 void
4204 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4205      int rf, ff, rt, ft;
4206      char promoChar;
4207      char move[7];
4208 {
4209     if (rf == DROP_RANK) {
4210         sprintf(move, "%c@%c%c\n",
4211                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4212     } else {
4213         if (promoChar == 'x' || promoChar == NULLCHAR) {
4214             sprintf(move, "%c%c%c%c\n",
4215                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4216         } else {
4217             sprintf(move, "%c%c%c%c%c\n",
4218                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4219         }
4220     }
4221 }
4222
4223 void
4224 ProcessICSInitScript(f)
4225      FILE *f;
4226 {
4227     char buf[MSG_SIZ];
4228
4229     while (fgets(buf, MSG_SIZ, f)) {
4230         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4231     }
4232
4233     fclose(f);
4234 }
4235
4236
4237 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4238 void
4239 AlphaRank(char *move, int n)
4240 {
4241 //    char *p = move, c; int x, y;
4242
4243     if (appData.debugMode) {
4244         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4245     }
4246
4247     if(move[1]=='*' && 
4248        move[2]>='0' && move[2]<='9' &&
4249        move[3]>='a' && move[3]<='x'    ) {
4250         move[1] = '@';
4251         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4252         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4253     } else
4254     if(move[0]>='0' && move[0]<='9' &&
4255        move[1]>='a' && move[1]<='x' &&
4256        move[2]>='0' && move[2]<='9' &&
4257        move[3]>='a' && move[3]<='x'    ) {
4258         /* input move, Shogi -> normal */
4259         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4260         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4261         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4262         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4263     } else
4264     if(move[1]=='@' &&
4265        move[3]>='0' && move[3]<='9' &&
4266        move[2]>='a' && move[2]<='x'    ) {
4267         move[1] = '*';
4268         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4269         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4270     } else
4271     if(
4272        move[0]>='a' && move[0]<='x' &&
4273        move[3]>='0' && move[3]<='9' &&
4274        move[2]>='a' && move[2]<='x'    ) {
4275          /* output move, normal -> Shogi */
4276         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4277         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4278         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4279         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4280         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4281     }
4282     if (appData.debugMode) {
4283         fprintf(debugFP, "   out = '%s'\n", move);
4284     }
4285 }
4286
4287 /* Parser for moves from gnuchess, ICS, or user typein box */
4288 Boolean
4289 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4290      char *move;
4291      int moveNum;
4292      ChessMove *moveType;
4293      int *fromX, *fromY, *toX, *toY;
4294      char *promoChar;
4295 {       
4296     if (appData.debugMode) {
4297         fprintf(debugFP, "move to parse: %s\n", move);
4298     }
4299     *moveType = yylexstr(moveNum, move);
4300
4301     switch (*moveType) {
4302       case WhitePromotionChancellor:
4303       case BlackPromotionChancellor:
4304       case WhitePromotionArchbishop:
4305       case BlackPromotionArchbishop:
4306       case WhitePromotionQueen:
4307       case BlackPromotionQueen:
4308       case WhitePromotionRook:
4309       case BlackPromotionRook:
4310       case WhitePromotionBishop:
4311       case BlackPromotionBishop:
4312       case WhitePromotionKnight:
4313       case BlackPromotionKnight:
4314       case WhitePromotionKing:
4315       case BlackPromotionKing:
4316       case NormalMove:
4317       case WhiteCapturesEnPassant:
4318       case BlackCapturesEnPassant:
4319       case WhiteKingSideCastle:
4320       case WhiteQueenSideCastle:
4321       case BlackKingSideCastle:
4322       case BlackQueenSideCastle:
4323       case WhiteKingSideCastleWild:
4324       case WhiteQueenSideCastleWild:
4325       case BlackKingSideCastleWild:
4326       case BlackQueenSideCastleWild:
4327       /* Code added by Tord: */
4328       case WhiteHSideCastleFR:
4329       case WhiteASideCastleFR:
4330       case BlackHSideCastleFR:
4331       case BlackASideCastleFR:
4332       /* End of code added by Tord */
4333       case IllegalMove:         /* bug or odd chess variant */
4334         *fromX = currentMoveString[0] - AAA;
4335         *fromY = currentMoveString[1] - ONE;
4336         *toX = currentMoveString[2] - AAA;
4337         *toY = currentMoveString[3] - ONE;
4338         *promoChar = currentMoveString[4];
4339         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4340             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4341     if (appData.debugMode) {
4342         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4343     }
4344             *fromX = *fromY = *toX = *toY = 0;
4345             return FALSE;
4346         }
4347         if (appData.testLegality) {
4348           return (*moveType != IllegalMove);
4349         } else {
4350           return !(fromX == fromY && toX == toY);
4351         }
4352
4353       case WhiteDrop:
4354       case BlackDrop:
4355         *fromX = *moveType == WhiteDrop ?
4356           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4357           (int) CharToPiece(ToLower(currentMoveString[0]));
4358         *fromY = DROP_RANK;
4359         *toX = currentMoveString[2] - AAA;
4360         *toY = currentMoveString[3] - ONE;
4361         *promoChar = NULLCHAR;
4362         return TRUE;
4363
4364       case AmbiguousMove:
4365       case ImpossibleMove:
4366       case (ChessMove) 0:       /* end of file */
4367       case ElapsedTime:
4368       case Comment:
4369       case PGNTag:
4370       case NAG:
4371       case WhiteWins:
4372       case BlackWins:
4373       case GameIsDrawn:
4374       default:
4375     if (appData.debugMode) {
4376         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4377     }
4378         /* bug? */
4379         *fromX = *fromY = *toX = *toY = 0;
4380         *promoChar = NULLCHAR;
4381         return FALSE;
4382     }
4383 }
4384
4385 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4386 // All positions will have equal probability, but the current method will not provide a unique
4387 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4388 #define DARK 1
4389 #define LITE 2
4390 #define ANY 3
4391
4392 int squaresLeft[4];
4393 int piecesLeft[(int)BlackPawn];
4394 int seed, nrOfShuffles;
4395
4396 void GetPositionNumber()
4397 {       // sets global variable seed
4398         int i;
4399
4400         seed = appData.defaultFrcPosition;
4401         if(seed < 0) { // randomize based on time for negative FRC position numbers
4402                 for(i=0; i<50; i++) seed += random();
4403                 seed = random() ^ random() >> 8 ^ random() << 8;
4404                 if(seed<0) seed = -seed;
4405         }
4406 }
4407
4408 int put(Board board, int pieceType, int rank, int n, int shade)
4409 // put the piece on the (n-1)-th empty squares of the given shade
4410 {
4411         int i;
4412
4413         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4414                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4415                         board[rank][i] = (ChessSquare) pieceType;
4416                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4417                         squaresLeft[ANY]--;
4418                         piecesLeft[pieceType]--; 
4419                         return i;
4420                 }
4421         }
4422         return -1;
4423 }
4424
4425
4426 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4427 // calculate where the next piece goes, (any empty square), and put it there
4428 {
4429         int i;
4430
4431         i = seed % squaresLeft[shade];
4432         nrOfShuffles *= squaresLeft[shade];
4433         seed /= squaresLeft[shade];
4434         put(board, pieceType, rank, i, shade);
4435 }
4436
4437 void AddTwoPieces(Board board, int pieceType, int rank)
4438 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4439 {
4440         int i, n=squaresLeft[ANY], j=n-1, k;
4441
4442         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4443         i = seed % k;  // pick one
4444         nrOfShuffles *= k;
4445         seed /= k;
4446         while(i >= j) i -= j--;
4447         j = n - 1 - j; i += j;
4448         put(board, pieceType, rank, j, ANY);
4449         put(board, pieceType, rank, i, ANY);
4450 }
4451
4452 void SetUpShuffle(Board board, int number)
4453 {
4454         int i, p, first=1;
4455
4456         GetPositionNumber(); nrOfShuffles = 1;
4457
4458         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4459         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4460         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4461
4462         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4463
4464         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4465             p = (int) board[0][i];
4466             if(p < (int) BlackPawn) piecesLeft[p] ++;
4467             board[0][i] = EmptySquare;
4468         }
4469
4470         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4471             // shuffles restricted to allow normal castling put KRR first
4472             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4473                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4474             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4475                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4476             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4477                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4478             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4479                 put(board, WhiteRook, 0, 0, ANY);
4480             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4481         }
4482
4483         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4484             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4485             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4486                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4487                 while(piecesLeft[p] >= 2) {
4488                     AddOnePiece(board, p, 0, LITE);
4489                     AddOnePiece(board, p, 0, DARK);
4490                 }
4491                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4492             }
4493
4494         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4495             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4496             // but we leave King and Rooks for last, to possibly obey FRC restriction
4497             if(p == (int)WhiteRook) continue;
4498             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4499             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4500         }
4501
4502         // now everything is placed, except perhaps King (Unicorn) and Rooks
4503
4504         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4505             // Last King gets castling rights
4506             while(piecesLeft[(int)WhiteUnicorn]) {
4507                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4508                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4509             }
4510
4511             while(piecesLeft[(int)WhiteKing]) {
4512                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4513                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4514             }
4515
4516
4517         } else {
4518             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4519             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4520         }
4521
4522         // Only Rooks can be left; simply place them all
4523         while(piecesLeft[(int)WhiteRook]) {
4524                 i = put(board, WhiteRook, 0, 0, ANY);
4525                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4526                         if(first) {
4527                                 first=0;
4528                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4529                         }
4530                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4531                 }
4532         }
4533         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4534             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4535         }
4536
4537         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4538 }
4539
4540 int SetCharTable( char *table, const char * map )
4541 /* [HGM] moved here from winboard.c because of its general usefulness */
4542 /*       Basically a safe strcpy that uses the last character as King */
4543 {
4544     int result = FALSE; int NrPieces;
4545
4546     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4547                     && NrPieces >= 12 && !(NrPieces&1)) {
4548         int i; /* [HGM] Accept even length from 12 to 34 */
4549
4550         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4551         for( i=0; i<NrPieces/2-1; i++ ) {
4552             table[i] = map[i];
4553             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4554         }
4555         table[(int) WhiteKing]  = map[NrPieces/2-1];
4556         table[(int) BlackKing]  = map[NrPieces-1];
4557
4558         result = TRUE;
4559     }
4560
4561     return result;
4562 }
4563
4564 void Prelude(Board board)
4565 {       // [HGM] superchess: random selection of exo-pieces
4566         int i, j, k; ChessSquare p; 
4567         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4568
4569         GetPositionNumber(); // use FRC position number
4570
4571         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4572             SetCharTable(pieceToChar, appData.pieceToCharTable);
4573             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4574                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4575         }
4576
4577         j = seed%4;                 seed /= 4; 
4578         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4579         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4580         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4581         j = seed%3 + (seed%3 >= j); seed /= 3; 
4582         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4583         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4584         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4585         j = seed%3;                 seed /= 3; 
4586         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4587         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4588         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4589         j = seed%2 + (seed%2 >= j); seed /= 2; 
4590         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4591         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4592         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4593         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4594         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4595         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4596         put(board, exoPieces[0],    0, 0, ANY);
4597         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4598 }
4599
4600 void
4601 InitPosition(redraw)
4602      int redraw;
4603 {
4604     ChessSquare (* pieces)[BOARD_SIZE];
4605     int i, j, pawnRow, overrule,
4606     oldx = gameInfo.boardWidth,
4607     oldy = gameInfo.boardHeight,
4608     oldh = gameInfo.holdingsWidth,
4609     oldv = gameInfo.variant;
4610
4611     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4612
4613     /* [AS] Initialize pv info list [HGM] and game status */
4614     {
4615         for( i=0; i<MAX_MOVES; i++ ) {
4616             pvInfoList[i].depth = 0;
4617             epStatus[i]=EP_NONE;
4618             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4619         }
4620
4621         initialRulePlies = 0; /* 50-move counter start */
4622
4623         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4624         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4625     }
4626
4627     
4628     /* [HGM] logic here is completely changed. In stead of full positions */
4629     /* the initialized data only consist of the two backranks. The switch */
4630     /* selects which one we will use, which is than copied to the Board   */
4631     /* initialPosition, which for the rest is initialized by Pawns and    */
4632     /* empty squares. This initial position is then copied to boards[0],  */
4633     /* possibly after shuffling, so that it remains available.            */
4634
4635     gameInfo.holdingsWidth = 0; /* default board sizes */
4636     gameInfo.boardWidth    = 8;
4637     gameInfo.boardHeight   = 8;
4638     gameInfo.holdingsSize  = 0;
4639     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4640     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4641     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4642
4643     switch (gameInfo.variant) {
4644     case VariantFischeRandom:
4645       shuffleOpenings = TRUE;
4646     default:
4647       pieces = FIDEArray;
4648       break;
4649     case VariantShatranj:
4650       pieces = ShatranjArray;
4651       nrCastlingRights = 0;
4652       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4653       break;
4654     case VariantTwoKings:
4655       pieces = twoKingsArray;
4656       break;
4657     case VariantCapaRandom:
4658       shuffleOpenings = TRUE;
4659     case VariantCapablanca:
4660       pieces = CapablancaArray;
4661       gameInfo.boardWidth = 10;
4662       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4663       break;
4664     case VariantGothic:
4665       pieces = GothicArray;
4666       gameInfo.boardWidth = 10;
4667       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4668       break;
4669     case VariantJanus:
4670       pieces = JanusArray;
4671       gameInfo.boardWidth = 10;
4672       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4673       nrCastlingRights = 6;
4674         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4675         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4676         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4677         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4678         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4679         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4680       break;
4681     case VariantFalcon:
4682       pieces = FalconArray;
4683       gameInfo.boardWidth = 10;
4684       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4685       break;
4686     case VariantXiangqi:
4687       pieces = XiangqiArray;
4688       gameInfo.boardWidth  = 9;
4689       gameInfo.boardHeight = 10;
4690       nrCastlingRights = 0;
4691       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4692       break;
4693     case VariantShogi:
4694       pieces = ShogiArray;
4695       gameInfo.boardWidth  = 9;
4696       gameInfo.boardHeight = 9;
4697       gameInfo.holdingsSize = 7;
4698       nrCastlingRights = 0;
4699       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4700       break;
4701     case VariantCourier:
4702       pieces = CourierArray;
4703       gameInfo.boardWidth  = 12;
4704       nrCastlingRights = 0;
4705       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4706       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4707       break;
4708     case VariantKnightmate:
4709       pieces = KnightmateArray;
4710       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4711       break;
4712     case VariantFairy:
4713       pieces = fairyArray;
4714       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4715       break;
4716     case VariantGreat:
4717       pieces = GreatArray;
4718       gameInfo.boardWidth = 10;
4719       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4720       gameInfo.holdingsSize = 8;
4721       break;
4722     case VariantSuper:
4723       pieces = FIDEArray;
4724       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4725       gameInfo.holdingsSize = 8;
4726       startedFromSetupPosition = TRUE;
4727       break;
4728     case VariantCrazyhouse:
4729     case VariantBughouse:
4730       pieces = FIDEArray;
4731       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4732       gameInfo.holdingsSize = 5;
4733       break;
4734     case VariantWildCastle:
4735       pieces = FIDEArray;
4736       /* !!?shuffle with kings guaranteed to be on d or e file */
4737       shuffleOpenings = 1;
4738       break;
4739     case VariantNoCastle:
4740       pieces = FIDEArray;
4741       nrCastlingRights = 0;
4742       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4743       /* !!?unconstrained back-rank shuffle */
4744       shuffleOpenings = 1;
4745       break;
4746     }
4747
4748     overrule = 0;
4749     if(appData.NrFiles >= 0) {
4750         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4751         gameInfo.boardWidth = appData.NrFiles;
4752     }
4753     if(appData.NrRanks >= 0) {
4754         gameInfo.boardHeight = appData.NrRanks;
4755     }
4756     if(appData.holdingsSize >= 0) {
4757         i = appData.holdingsSize;
4758         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4759         gameInfo.holdingsSize = i;
4760     }
4761     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4762     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4763         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4764
4765     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4766     if(pawnRow < 1) pawnRow = 1;
4767
4768     /* User pieceToChar list overrules defaults */
4769     if(appData.pieceToCharTable != NULL)
4770         SetCharTable(pieceToChar, appData.pieceToCharTable);
4771
4772     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4773
4774         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4775             s = (ChessSquare) 0; /* account holding counts in guard band */
4776         for( i=0; i<BOARD_HEIGHT; i++ )
4777             initialPosition[i][j] = s;
4778
4779         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4780         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4781         initialPosition[pawnRow][j] = WhitePawn;
4782         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4783         if(gameInfo.variant == VariantXiangqi) {
4784             if(j&1) {
4785                 initialPosition[pawnRow][j] = 
4786                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4787                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4788                    initialPosition[2][j] = WhiteCannon;
4789                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4790                 }
4791             }
4792         }
4793         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4794     }
4795     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4796
4797             j=BOARD_LEFT+1;
4798             initialPosition[1][j] = WhiteBishop;
4799             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4800             j=BOARD_RGHT-2;
4801             initialPosition[1][j] = WhiteRook;
4802             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4803     }
4804
4805     if( nrCastlingRights == -1) {
4806         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4807         /*       This sets default castling rights from none to normal corners   */
4808         /* Variants with other castling rights must set them themselves above    */
4809         nrCastlingRights = 6;
4810        
4811         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4812         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4813         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4814         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4815         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4816         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4817      }
4818
4819      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4820      if(gameInfo.variant == VariantGreat) { // promotion commoners
4821         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4822         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4823         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4824         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4825      }
4826   if (appData.debugMode) {
4827     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4828   }
4829     if(shuffleOpenings) {
4830         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4831         startedFromSetupPosition = TRUE;
4832     }
4833     if(startedFromPositionFile) {
4834       /* [HGM] loadPos: use PositionFile for every new game */
4835       CopyBoard(initialPosition, filePosition);
4836       for(i=0; i<nrCastlingRights; i++)
4837           castlingRights[0][i] = initialRights[i] = fileRights[i];
4838       startedFromSetupPosition = TRUE;
4839     }
4840
4841     CopyBoard(boards[0], initialPosition);
4842
4843     if(oldx != gameInfo.boardWidth ||
4844        oldy != gameInfo.boardHeight ||
4845        oldh != gameInfo.holdingsWidth
4846 #ifdef GOTHIC
4847        || oldv == VariantGothic ||        // For licensing popups
4848        gameInfo.variant == VariantGothic
4849 #endif
4850 #ifdef FALCON
4851        || oldv == VariantFalcon ||
4852        gameInfo.variant == VariantFalcon
4853 #endif
4854                                          )
4855             InitDrawingSizes(-2 ,0);
4856
4857     if (redraw)
4858       DrawPosition(TRUE, boards[currentMove]);
4859 }
4860
4861 void
4862 SendBoard(cps, moveNum)
4863      ChessProgramState *cps;
4864      int moveNum;
4865 {
4866     char message[MSG_SIZ];
4867     
4868     if (cps->useSetboard) {
4869       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4870       sprintf(message, "setboard %s\n", fen);
4871       SendToProgram(message, cps);
4872       free(fen);
4873
4874     } else {
4875       ChessSquare *bp;
4876       int i, j;
4877       /* Kludge to set black to move, avoiding the troublesome and now
4878        * deprecated "black" command.
4879        */
4880       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4881
4882       SendToProgram("edit\n", cps);
4883       SendToProgram("#\n", cps);
4884       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4885         bp = &boards[moveNum][i][BOARD_LEFT];
4886         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4887           if ((int) *bp < (int) BlackPawn) {
4888             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4889                     AAA + j, ONE + i);
4890             if(message[0] == '+' || message[0] == '~') {
4891                 sprintf(message, "%c%c%c+\n",
4892                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4893                         AAA + j, ONE + i);
4894             }
4895             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4896                 message[1] = BOARD_RGHT   - 1 - j + '1';
4897                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4898             }
4899             SendToProgram(message, cps);
4900           }
4901         }
4902       }
4903     
4904       SendToProgram("c\n", cps);
4905       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4906         bp = &boards[moveNum][i][BOARD_LEFT];
4907         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4908           if (((int) *bp != (int) EmptySquare)
4909               && ((int) *bp >= (int) BlackPawn)) {
4910             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4911                     AAA + j, ONE + i);
4912             if(message[0] == '+' || message[0] == '~') {
4913                 sprintf(message, "%c%c%c+\n",
4914                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4915                         AAA + j, ONE + i);
4916             }
4917             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4918                 message[1] = BOARD_RGHT   - 1 - j + '1';
4919                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4920             }
4921             SendToProgram(message, cps);
4922           }
4923         }
4924       }
4925     
4926       SendToProgram(".\n", cps);
4927     }
4928     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4929 }
4930
4931 int
4932 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4933 {
4934     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4935     /* [HGM] add Shogi promotions */
4936     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4937     ChessSquare piece;
4938     ChessMove moveType;
4939     Boolean premove;
4940
4941     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4942     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4943
4944     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4945       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4946         return FALSE;
4947
4948     piece = boards[currentMove][fromY][fromX];
4949     if(gameInfo.variant == VariantShogi) {
4950         promotionZoneSize = 3;
4951         highestPromotingPiece = (int)WhiteFerz;
4952     }
4953
4954     // next weed out all moves that do not touch the promotion zone at all
4955     if((int)piece >= BlackPawn) {
4956         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4957              return FALSE;
4958         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4959     } else {
4960         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4961            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4962     }
4963
4964     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4965
4966     // weed out mandatory Shogi promotions
4967     if(gameInfo.variant == VariantShogi) {
4968         if(piece >= BlackPawn) {
4969             if(toY == 0 && piece == BlackPawn ||
4970                toY == 0 && piece == BlackQueen ||
4971                toY <= 1 && piece == BlackKnight) {
4972                 *promoChoice = '+';
4973                 return FALSE;
4974             }
4975         } else {
4976             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4977                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4978                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4979                 *promoChoice = '+';
4980                 return FALSE;
4981             }
4982         }
4983     }
4984
4985     // weed out obviously illegal Pawn moves
4986     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4987         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4988         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4989         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4990         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4991         // note we are not allowed to test for valid (non-)capture, due to premove
4992     }
4993
4994     // we either have a choice what to promote to, or (in Shogi) whether to promote
4995     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4996         *promoChoice = PieceToChar(BlackFerz);  // no choice
4997         return FALSE;
4998     }
4999     if(appData.alwaysPromoteToQueen) { // predetermined
5000         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5001              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5002         else *promoChoice = PieceToChar(BlackQueen);
5003         return FALSE;
5004     }
5005
5006     // suppress promotion popup on illegal moves that are not premoves
5007     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5008               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5009     if(appData.testLegality && !premove) {
5010         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5011                         epStatus[currentMove], castlingRights[currentMove],
5012                         fromY, fromX, toY, toX, NULLCHAR);
5013         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5014            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5015             return FALSE;
5016     }
5017
5018     return TRUE;
5019 }
5020
5021 int
5022 InPalace(row, column)
5023      int row, column;
5024 {   /* [HGM] for Xiangqi */
5025     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5026          column < (BOARD_WIDTH + 4)/2 &&
5027          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5028     return FALSE;
5029 }
5030
5031 int
5032 PieceForSquare (x, y)
5033      int x;
5034      int y;
5035 {
5036   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5037      return -1;
5038   else
5039      return boards[currentMove][y][x];
5040 }
5041
5042 int
5043 OKToStartUserMove(x, y)
5044      int x, y;
5045 {
5046     ChessSquare from_piece;
5047     int white_piece;
5048
5049     if (matchMode) return FALSE;
5050     if (gameMode == EditPosition) return TRUE;
5051
5052     if (x >= 0 && y >= 0)
5053       from_piece = boards[currentMove][y][x];
5054     else
5055       from_piece = EmptySquare;
5056
5057     if (from_piece == EmptySquare) return FALSE;
5058
5059     white_piece = (int)from_piece >= (int)WhitePawn &&
5060       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5061
5062     switch (gameMode) {
5063       case PlayFromGameFile:
5064       case AnalyzeFile:
5065       case TwoMachinesPlay:
5066       case EndOfGame:
5067         return FALSE;
5068
5069       case IcsObserving:
5070       case IcsIdle:
5071         return FALSE;
5072
5073       case MachinePlaysWhite:
5074       case IcsPlayingBlack:
5075         if (appData.zippyPlay) return FALSE;
5076         if (white_piece) {
5077             DisplayMoveError(_("You are playing Black"));
5078             return FALSE;
5079         }
5080         break;
5081
5082       case MachinePlaysBlack:
5083       case IcsPlayingWhite:
5084         if (appData.zippyPlay) return FALSE;
5085         if (!white_piece) {
5086             DisplayMoveError(_("You are playing White"));
5087             return FALSE;
5088         }
5089         break;
5090
5091       case EditGame:
5092         if (!white_piece && WhiteOnMove(currentMove)) {
5093             DisplayMoveError(_("It is White's turn"));
5094             return FALSE;
5095         }           
5096         if (white_piece && !WhiteOnMove(currentMove)) {
5097             DisplayMoveError(_("It is Black's turn"));
5098             return FALSE;
5099         }           
5100         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5101             /* Editing correspondence game history */
5102             /* Could disallow this or prompt for confirmation */
5103             cmailOldMove = -1;
5104         }
5105         if (currentMove < forwardMostMove) {
5106             /* Discarding moves */
5107             /* Could prompt for confirmation here,
5108                but I don't think that's such a good idea */
5109             forwardMostMove = currentMove;
5110         }
5111         break;
5112
5113       case BeginningOfGame:
5114         if (appData.icsActive) return FALSE;
5115         if (!appData.noChessProgram) {
5116             if (!white_piece) {
5117                 DisplayMoveError(_("You are playing White"));
5118                 return FALSE;
5119             }
5120         }
5121         break;
5122         
5123       case Training:
5124         if (!white_piece && WhiteOnMove(currentMove)) {
5125             DisplayMoveError(_("It is White's turn"));
5126             return FALSE;
5127         }           
5128         if (white_piece && !WhiteOnMove(currentMove)) {
5129             DisplayMoveError(_("It is Black's turn"));
5130             return FALSE;
5131         }           
5132         break;
5133
5134       default:
5135       case IcsExamining:
5136         break;
5137     }
5138     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5139         && gameMode != AnalyzeFile && gameMode != Training) {
5140         DisplayMoveError(_("Displayed position is not current"));
5141         return FALSE;
5142     }
5143     return TRUE;
5144 }
5145
5146 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5147 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5148 int lastLoadGameUseList = FALSE;
5149 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5150 ChessMove lastLoadGameStart = (ChessMove) 0;
5151
5152 ChessMove
5153 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5154      int fromX, fromY, toX, toY;
5155      int promoChar;
5156      Boolean captureOwn;
5157 {
5158     ChessMove moveType;
5159     ChessSquare pdown, pup;
5160
5161     /* Check if the user is playing in turn.  This is complicated because we
5162        let the user "pick up" a piece before it is his turn.  So the piece he
5163        tried to pick up may have been captured by the time he puts it down!
5164        Therefore we use the color the user is supposed to be playing in this
5165        test, not the color of the piece that is currently on the starting
5166        square---except in EditGame mode, where the user is playing both
5167        sides; fortunately there the capture race can't happen.  (It can
5168        now happen in IcsExamining mode, but that's just too bad.  The user
5169        will get a somewhat confusing message in that case.)
5170        */
5171
5172     switch (gameMode) {
5173       case PlayFromGameFile:
5174       case AnalyzeFile:
5175       case TwoMachinesPlay:
5176       case EndOfGame:
5177       case IcsObserving:
5178       case IcsIdle:
5179         /* We switched into a game mode where moves are not accepted,
5180            perhaps while the mouse button was down. */
5181         return ImpossibleMove;
5182
5183       case MachinePlaysWhite:
5184         /* User is moving for Black */
5185         if (WhiteOnMove(currentMove)) {
5186             DisplayMoveError(_("It is White's turn"));
5187             return ImpossibleMove;
5188         }
5189         break;
5190
5191       case MachinePlaysBlack:
5192         /* User is moving for White */
5193         if (!WhiteOnMove(currentMove)) {
5194             DisplayMoveError(_("It is Black's turn"));
5195             return ImpossibleMove;
5196         }
5197         break;
5198
5199       case EditGame:
5200       case IcsExamining:
5201       case BeginningOfGame:
5202       case AnalyzeMode:
5203       case Training:
5204         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5205             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5206             /* User is moving for Black */
5207             if (WhiteOnMove(currentMove)) {
5208                 DisplayMoveError(_("It is White's turn"));
5209                 return ImpossibleMove;
5210             }
5211         } else {
5212             /* User is moving for White */
5213             if (!WhiteOnMove(currentMove)) {
5214                 DisplayMoveError(_("It is Black's turn"));
5215                 return ImpossibleMove;
5216             }
5217         }
5218         break;
5219
5220       case IcsPlayingBlack:
5221         /* User is moving for Black */
5222         if (WhiteOnMove(currentMove)) {
5223             if (!appData.premove) {
5224                 DisplayMoveError(_("It is White's turn"));
5225             } else if (toX >= 0 && toY >= 0) {
5226                 premoveToX = toX;
5227                 premoveToY = toY;
5228                 premoveFromX = fromX;
5229                 premoveFromY = fromY;
5230                 premovePromoChar = promoChar;
5231                 gotPremove = 1;
5232                 if (appData.debugMode) 
5233                     fprintf(debugFP, "Got premove: fromX %d,"
5234                             "fromY %d, toX %d, toY %d\n",
5235                             fromX, fromY, toX, toY);
5236             }
5237             return ImpossibleMove;
5238         }
5239         break;
5240
5241       case IcsPlayingWhite:
5242         /* User is moving for White */
5243         if (!WhiteOnMove(currentMove)) {
5244             if (!appData.premove) {
5245                 DisplayMoveError(_("It is Black's turn"));
5246             } else if (toX >= 0 && toY >= 0) {
5247                 premoveToX = toX;
5248                 premoveToY = toY;
5249                 premoveFromX = fromX;
5250                 premoveFromY = fromY;
5251                 premovePromoChar = promoChar;
5252                 gotPremove = 1;
5253                 if (appData.debugMode) 
5254                     fprintf(debugFP, "Got premove: fromX %d,"
5255                             "fromY %d, toX %d, toY %d\n",
5256                             fromX, fromY, toX, toY);
5257             }
5258             return ImpossibleMove;
5259         }
5260         break;
5261
5262       default:
5263         break;
5264
5265       case EditPosition:
5266         /* EditPosition, empty square, or different color piece;
5267            click-click move is possible */
5268         if (toX == -2 || toY == -2) {
5269             boards[0][fromY][fromX] = EmptySquare;
5270             return AmbiguousMove;
5271         } else if (toX >= 0 && toY >= 0) {
5272             boards[0][toY][toX] = boards[0][fromY][fromX];
5273             boards[0][fromY][fromX] = EmptySquare;
5274             return AmbiguousMove;
5275         }
5276         return ImpossibleMove;
5277     }
5278
5279     if(toX < 0 || toY < 0) return ImpossibleMove;
5280     pdown = boards[currentMove][fromY][fromX];
5281     pup = boards[currentMove][toY][toX];
5282
5283     /* [HGM] If move started in holdings, it means a drop */
5284     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5285          if( pup != EmptySquare ) return ImpossibleMove;
5286          if(appData.testLegality) {
5287              /* it would be more logical if LegalityTest() also figured out
5288               * which drops are legal. For now we forbid pawns on back rank.
5289               * Shogi is on its own here...
5290               */
5291              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5292                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5293                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5294          }
5295          return WhiteDrop; /* Not needed to specify white or black yet */
5296     }
5297
5298     userOfferedDraw = FALSE;
5299         
5300     /* [HGM] always test for legality, to get promotion info */
5301     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5302                           epStatus[currentMove], castlingRights[currentMove],
5303                                          fromY, fromX, toY, toX, promoChar);
5304     /* [HGM] but possibly ignore an IllegalMove result */
5305     if (appData.testLegality) {
5306         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5307             DisplayMoveError(_("Illegal move"));
5308             return ImpossibleMove;
5309         }
5310     }
5311 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5312     return moveType;
5313     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5314        function is made into one that returns an OK move type if FinishMove
5315        should be called. This to give the calling driver routine the
5316        opportunity to finish the userMove input with a promotion popup,
5317        without bothering the user with this for invalid or illegal moves */
5318
5319 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5320 }
5321
5322 /* Common tail of UserMoveEvent and DropMenuEvent */
5323 int
5324 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5325      ChessMove moveType;
5326      int fromX, fromY, toX, toY;
5327      /*char*/int promoChar;
5328 {
5329     char *bookHit = 0;
5330 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5331     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5332         // [HGM] superchess: suppress promotions to non-available piece
5333         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5334         if(WhiteOnMove(currentMove)) {
5335             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5336         } else {
5337             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5338         }
5339     }
5340
5341     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5342        move type in caller when we know the move is a legal promotion */
5343     if(moveType == NormalMove && promoChar)
5344         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5345 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5346     /* [HGM] convert drag-and-drop piece drops to standard form */
5347     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5348          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5349            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5350                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5351 //         fromX = boards[currentMove][fromY][fromX];
5352            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5353            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5354            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5355            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5356          fromY = DROP_RANK;
5357     }
5358
5359     /* [HGM] <popupFix> The following if has been moved here from
5360        UserMoveEvent(). Because it seemed to belon here (why not allow
5361        piece drops in training games?), and because it can only be
5362        performed after it is known to what we promote. */
5363     if (gameMode == Training) {
5364       /* compare the move played on the board to the next move in the
5365        * game. If they match, display the move and the opponent's response. 
5366        * If they don't match, display an error message.
5367        */
5368       int saveAnimate;
5369       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5370       CopyBoard(testBoard, boards[currentMove]);
5371       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5372
5373       if (CompareBoards(testBoard, boards[currentMove+1])) {
5374         ForwardInner(currentMove+1);
5375
5376         /* Autoplay the opponent's response.
5377          * if appData.animate was TRUE when Training mode was entered,
5378          * the response will be animated.
5379          */
5380         saveAnimate = appData.animate;
5381         appData.animate = animateTraining;
5382         ForwardInner(currentMove+1);
5383         appData.animate = saveAnimate;
5384
5385         /* check for the end of the game */
5386         if (currentMove >= forwardMostMove) {
5387           gameMode = PlayFromGameFile;
5388           ModeHighlight();
5389           SetTrainingModeOff();
5390           DisplayInformation(_("End of game"));
5391         }
5392       } else {
5393         DisplayError(_("Incorrect move"), 0);
5394       }
5395       return 1;
5396     }
5397
5398   /* Ok, now we know that the move is good, so we can kill
5399      the previous line in Analysis Mode */
5400   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5401     forwardMostMove = currentMove;
5402   }
5403
5404   /* If we need the chess program but it's dead, restart it */
5405   ResurrectChessProgram();
5406
5407   /* A user move restarts a paused game*/
5408   if (pausing)
5409     PauseEvent();
5410
5411   thinkOutput[0] = NULLCHAR;
5412
5413   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5414
5415   if (gameMode == BeginningOfGame) {
5416     if (appData.noChessProgram) {
5417       gameMode = EditGame;
5418       SetGameInfo();
5419     } else {
5420       char buf[MSG_SIZ];
5421       gameMode = MachinePlaysBlack;
5422       StartClocks();
5423       SetGameInfo();
5424       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5425       DisplayTitle(buf);
5426       if (first.sendName) {
5427         sprintf(buf, "name %s\n", gameInfo.white);
5428         SendToProgram(buf, &first);
5429       }
5430       StartClocks();
5431     }
5432     ModeHighlight();
5433   }
5434 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5435   /* Relay move to ICS or chess engine */
5436   if (appData.icsActive) {
5437     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5438         gameMode == IcsExamining) {
5439       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5440       ics_user_moved = 1;
5441     }
5442   } else {
5443     if (first.sendTime && (gameMode == BeginningOfGame ||
5444                            gameMode == MachinePlaysWhite ||
5445                            gameMode == MachinePlaysBlack)) {
5446       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5447     }
5448     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5449          // [HGM] book: if program might be playing, let it use book
5450         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5451         first.maybeThinking = TRUE;
5452     } else SendMoveToProgram(forwardMostMove-1, &first);
5453     if (currentMove == cmailOldMove + 1) {
5454       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5455     }
5456   }
5457
5458   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5459
5460   switch (gameMode) {
5461   case EditGame:
5462     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5463                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5464     case MT_NONE:
5465     case MT_CHECK:
5466       break;
5467     case MT_CHECKMATE:
5468     case MT_STAINMATE:
5469       if (WhiteOnMove(currentMove)) {
5470         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5471       } else {
5472         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5473       }
5474       break;
5475     case MT_STALEMATE:
5476       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5477       break;
5478     }
5479     break;
5480     
5481   case MachinePlaysBlack:
5482   case MachinePlaysWhite:
5483     /* disable certain menu options while machine is thinking */
5484     SetMachineThinkingEnables();
5485     break;
5486
5487   default:
5488     break;
5489   }
5490
5491   if(bookHit) { // [HGM] book: simulate book reply
5492         static char bookMove[MSG_SIZ]; // a bit generous?
5493
5494         programStats.nodes = programStats.depth = programStats.time = 
5495         programStats.score = programStats.got_only_move = 0;
5496         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5497
5498         strcpy(bookMove, "move ");
5499         strcat(bookMove, bookHit);
5500         HandleMachineMove(bookMove, &first);
5501   }
5502   return 1;
5503 }
5504
5505 void
5506 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5507      int fromX, fromY, toX, toY;
5508      int promoChar;
5509 {
5510     /* [HGM] This routine was added to allow calling of its two logical
5511        parts from other modules in the old way. Before, UserMoveEvent()
5512        automatically called FinishMove() if the move was OK, and returned
5513        otherwise. I separated the two, in order to make it possible to
5514        slip a promotion popup in between. But that it always needs two
5515        calls, to the first part, (now called UserMoveTest() ), and to
5516        FinishMove if the first part succeeded. Calls that do not need
5517        to do anything in between, can call this routine the old way. 
5518     */
5519     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5520 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5521     if(moveType == AmbiguousMove)
5522         DrawPosition(FALSE, boards[currentMove]);
5523     else if(moveType != ImpossibleMove && moveType != Comment)
5524         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5525 }
5526
5527 void LeftClick(ClickType clickType, int xPix, int yPix)
5528 {
5529     int x, y;
5530     Boolean saveAnimate;
5531     static int second = 0, promotionChoice = 0;
5532     char promoChoice = NULLCHAR;
5533
5534     if (clickType == Press) ErrorPopDown();
5535
5536     x = EventToSquare(xPix, BOARD_WIDTH);
5537     y = EventToSquare(yPix, BOARD_HEIGHT);
5538     if (!flipView && y >= 0) {
5539         y = BOARD_HEIGHT - 1 - y;
5540     }
5541     if (flipView && x >= 0) {
5542         x = BOARD_WIDTH - 1 - x;
5543     }
5544
5545     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5546         if(clickType == Release) return; // ignore upclick of click-click destination
5547         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5548         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5549         if(gameInfo.holdingsWidth && 
5550                 (WhiteOnMove(currentMove) 
5551                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5552                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5553             // click in right holdings, for determining promotion piece
5554             ChessSquare p = boards[currentMove][y][x];
5555             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5556             if(p != EmptySquare) {
5557                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5558                 fromX = fromY = -1;
5559                 return;
5560             }
5561         }
5562         DrawPosition(FALSE, boards[currentMove]);
5563         return;
5564     }
5565
5566     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5567     if(clickType == Press
5568             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5569               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5570               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5571         return;
5572
5573     if (fromX == -1) {
5574         if (clickType == Press) {
5575             /* First square */
5576             if (OKToStartUserMove(x, y)) {
5577                 fromX = x;
5578                 fromY = y;
5579                 second = 0;
5580                 DragPieceBegin(xPix, yPix);
5581                 if (appData.highlightDragging) {
5582                     SetHighlights(x, y, -1, -1);
5583                 }
5584             }
5585         }
5586         return;
5587     }
5588
5589     /* fromX != -1 */
5590     if (clickType == Press && gameMode != EditPosition) {
5591         ChessSquare fromP;
5592         ChessSquare toP;
5593         int frc;
5594
5595         // ignore off-board to clicks
5596         if(y < 0 || x < 0) return;
5597
5598         /* Check if clicking again on the same color piece */
5599         fromP = boards[currentMove][fromY][fromX];
5600         toP = boards[currentMove][y][x];
5601         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5602         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5603              WhitePawn <= toP && toP <= WhiteKing &&
5604              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5605              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5606             (BlackPawn <= fromP && fromP <= BlackKing && 
5607              BlackPawn <= toP && toP <= BlackKing &&
5608              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5609              !(fromP == BlackKing && toP == BlackRook && frc))) {
5610             /* Clicked again on same color piece -- changed his mind */
5611             second = (x == fromX && y == fromY);
5612             if (appData.highlightDragging) {
5613                 SetHighlights(x, y, -1, -1);
5614             } else {
5615                 ClearHighlights();
5616             }
5617             if (OKToStartUserMove(x, y)) {
5618                 fromX = x;
5619                 fromY = y;
5620                 DragPieceBegin(xPix, yPix);
5621             }
5622             return;
5623         }
5624         // ignore clicks on holdings
5625         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5626     }
5627
5628     if (clickType == Release && x == fromX && y == fromY) {
5629         DragPieceEnd(xPix, yPix);
5630         if (appData.animateDragging) {
5631             /* Undo animation damage if any */
5632             DrawPosition(FALSE, NULL);
5633         }
5634         if (second) {
5635             /* Second up/down in same square; just abort move */
5636             second = 0;
5637             fromX = fromY = -1;
5638             ClearHighlights();
5639             gotPremove = 0;
5640             ClearPremoveHighlights();
5641         } else {
5642             /* First upclick in same square; start click-click mode */
5643             SetHighlights(x, y, -1, -1);
5644         }
5645         return;
5646     }
5647
5648     /* we now have a different from- and (possibly off-board) to-square */
5649     /* Completed move */
5650     toX = x;
5651     toY = y;
5652     saveAnimate = appData.animate;
5653     if (clickType == Press) {
5654         /* Finish clickclick move */
5655         if (appData.animate || appData.highlightLastMove) {
5656             SetHighlights(fromX, fromY, toX, toY);
5657         } else {
5658             ClearHighlights();
5659         }
5660     } else {
5661         /* Finish drag move */
5662         if (appData.highlightLastMove) {
5663             SetHighlights(fromX, fromY, toX, toY);
5664         } else {
5665             ClearHighlights();
5666         }
5667         DragPieceEnd(xPix, yPix);
5668         /* Don't animate move and drag both */
5669         appData.animate = FALSE;
5670     }
5671
5672     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5673     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5674         ClearHighlights();
5675         fromX = fromY = -1;
5676         return;
5677     }
5678
5679     // off-board moves should not be highlighted
5680     if(x < 0 || x < 0) ClearHighlights();
5681
5682     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5683         SetHighlights(fromX, fromY, toX, toY);
5684         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5685             // [HGM] super: promotion to captured piece selected from holdings
5686             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5687             promotionChoice = TRUE;
5688             // kludge follows to temporarily execute move on display, without promoting yet
5689             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5690             boards[currentMove][toY][toX] = p;
5691             DrawPosition(FALSE, boards[currentMove]);
5692             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5693             boards[currentMove][toY][toX] = q;
5694             DisplayMessage("Click in holdings to choose piece", "");
5695             return;
5696         }
5697         PromotionPopUp();
5698     } else {
5699         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5700         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5701         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5702         fromX = fromY = -1;
5703     }
5704     appData.animate = saveAnimate;
5705     if (appData.animate || appData.animateDragging) {
5706         /* Undo animation damage if needed */
5707         DrawPosition(FALSE, NULL);
5708     }
5709 }
5710
5711 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5712 {
5713 //    char * hint = lastHint;
5714     FrontEndProgramStats stats;
5715
5716     stats.which = cps == &first ? 0 : 1;
5717     stats.depth = cpstats->depth;
5718     stats.nodes = cpstats->nodes;
5719     stats.score = cpstats->score;
5720     stats.time = cpstats->time;
5721     stats.pv = cpstats->movelist;
5722     stats.hint = lastHint;
5723     stats.an_move_index = 0;
5724     stats.an_move_count = 0;
5725
5726     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5727         stats.hint = cpstats->move_name;
5728         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5729         stats.an_move_count = cpstats->nr_moves;
5730     }
5731
5732     SetProgramStats( &stats );
5733 }
5734
5735 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5736 {   // [HGM] book: this routine intercepts moves to simulate book replies
5737     char *bookHit = NULL;
5738
5739     //first determine if the incoming move brings opponent into his book
5740     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5741         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5742     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5743     if(bookHit != NULL && !cps->bookSuspend) {
5744         // make sure opponent is not going to reply after receiving move to book position
5745         SendToProgram("force\n", cps);
5746         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5747     }
5748     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5749     // now arrange restart after book miss
5750     if(bookHit) {
5751         // after a book hit we never send 'go', and the code after the call to this routine
5752         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5753         char buf[MSG_SIZ];
5754         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5755         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5756         SendToProgram(buf, cps);
5757         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5758     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5759         SendToProgram("go\n", cps);
5760         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5761     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5762         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5763             SendToProgram("go\n", cps); 
5764         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5765     }
5766     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5767 }
5768
5769 char *savedMessage;
5770 ChessProgramState *savedState;
5771 void DeferredBookMove(void)
5772 {
5773         if(savedState->lastPing != savedState->lastPong)
5774                     ScheduleDelayedEvent(DeferredBookMove, 10);
5775         else
5776         HandleMachineMove(savedMessage, savedState);
5777 }
5778
5779 void
5780 HandleMachineMove(message, cps)
5781      char *message;
5782      ChessProgramState *cps;
5783 {
5784     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5785     char realname[MSG_SIZ];
5786     int fromX, fromY, toX, toY;
5787     ChessMove moveType;
5788     char promoChar;
5789     char *p;
5790     int machineWhite;
5791     char *bookHit;
5792
5793 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5794     /*
5795      * Kludge to ignore BEL characters
5796      */
5797     while (*message == '\007') message++;
5798
5799     /*
5800      * [HGM] engine debug message: ignore lines starting with '#' character
5801      */
5802     if(cps->debug && *message == '#') return;
5803
5804     /*
5805      * Look for book output
5806      */
5807     if (cps == &first && bookRequested) {
5808         if (message[0] == '\t' || message[0] == ' ') {
5809             /* Part of the book output is here; append it */
5810             strcat(bookOutput, message);
5811             strcat(bookOutput, "  \n");
5812             return;
5813         } else if (bookOutput[0] != NULLCHAR) {
5814             /* All of book output has arrived; display it */
5815             char *p = bookOutput;
5816             while (*p != NULLCHAR) {
5817                 if (*p == '\t') *p = ' ';
5818                 p++;
5819             }
5820             DisplayInformation(bookOutput);
5821             bookRequested = FALSE;
5822             /* Fall through to parse the current output */
5823         }
5824     }
5825
5826     /*
5827      * Look for machine move.
5828      */
5829     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5830         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5831     {
5832         /* This method is only useful on engines that support ping */
5833         if (cps->lastPing != cps->lastPong) {
5834           if (gameMode == BeginningOfGame) {
5835             /* Extra move from before last new; ignore */
5836             if (appData.debugMode) {
5837                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5838             }
5839           } else {
5840             if (appData.debugMode) {
5841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5842                         cps->which, gameMode);
5843             }
5844
5845             SendToProgram("undo\n", cps);
5846           }
5847           return;
5848         }
5849
5850         switch (gameMode) {
5851           case BeginningOfGame:
5852             /* Extra move from before last reset; ignore */
5853             if (appData.debugMode) {
5854                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5855             }
5856             return;
5857
5858           case EndOfGame:
5859           case IcsIdle:
5860           default:
5861             /* Extra move after we tried to stop.  The mode test is
5862                not a reliable way of detecting this problem, but it's
5863                the best we can do on engines that don't support ping.
5864             */
5865             if (appData.debugMode) {
5866                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5867                         cps->which, gameMode);
5868             }
5869             SendToProgram("undo\n", cps);
5870             return;
5871
5872           case MachinePlaysWhite:
5873           case IcsPlayingWhite:
5874             machineWhite = TRUE;
5875             break;
5876
5877           case MachinePlaysBlack:
5878           case IcsPlayingBlack:
5879             machineWhite = FALSE;
5880             break;
5881
5882           case TwoMachinesPlay:
5883             machineWhite = (cps->twoMachinesColor[0] == 'w');
5884             break;
5885         }
5886         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5887             if (appData.debugMode) {
5888                 fprintf(debugFP,
5889                         "Ignoring move out of turn by %s, gameMode %d"
5890                         ", forwardMost %d\n",
5891                         cps->which, gameMode, forwardMostMove);
5892             }
5893             return;
5894         }
5895
5896     if (appData.debugMode) { int f = forwardMostMove;
5897         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5898                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5899     }
5900         if(cps->alphaRank) AlphaRank(machineMove, 4);
5901         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5902                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5903             /* Machine move could not be parsed; ignore it. */
5904             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5905                     machineMove, cps->which);
5906             DisplayError(buf1, 0);
5907             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5908                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5909             if (gameMode == TwoMachinesPlay) {
5910               GameEnds(machineWhite ? BlackWins : WhiteWins,
5911                        buf1, GE_XBOARD);
5912             }
5913             return;
5914         }
5915
5916         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5917         /* So we have to redo legality test with true e.p. status here,  */
5918         /* to make sure an illegal e.p. capture does not slip through,   */
5919         /* to cause a forfeit on a justified illegal-move complaint      */
5920         /* of the opponent.                                              */
5921         if( gameMode==TwoMachinesPlay && appData.testLegality
5922             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5923                                                               ) {
5924            ChessMove moveType;
5925            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5926                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5927                              fromY, fromX, toY, toX, promoChar);
5928             if (appData.debugMode) {
5929                 int i;
5930                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5931                     castlingRights[forwardMostMove][i], castlingRank[i]);
5932                 fprintf(debugFP, "castling rights\n");
5933             }
5934             if(moveType == IllegalMove) {
5935                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5936                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5937                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5938                            buf1, GE_XBOARD);
5939                 return;
5940            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5941            /* [HGM] Kludge to handle engines that send FRC-style castling
5942               when they shouldn't (like TSCP-Gothic) */
5943            switch(moveType) {
5944              case WhiteASideCastleFR:
5945              case BlackASideCastleFR:
5946                toX+=2;
5947                currentMoveString[2]++;
5948                break;
5949              case WhiteHSideCastleFR:
5950              case BlackHSideCastleFR:
5951                toX--;
5952                currentMoveString[2]--;
5953                break;
5954              default: ; // nothing to do, but suppresses warning of pedantic compilers
5955            }
5956         }
5957         hintRequested = FALSE;
5958         lastHint[0] = NULLCHAR;
5959         bookRequested = FALSE;
5960         /* Program may be pondering now */
5961         cps->maybeThinking = TRUE;
5962         if (cps->sendTime == 2) cps->sendTime = 1;
5963         if (cps->offeredDraw) cps->offeredDraw--;
5964
5965 #if ZIPPY
5966         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5967             first.initDone) {
5968           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5969           ics_user_moved = 1;
5970           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5971                 char buf[3*MSG_SIZ];
5972
5973                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5974                         programStats.score / 100.,
5975                         programStats.depth,
5976                         programStats.time / 100.,
5977                         (unsigned int)programStats.nodes,
5978                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5979                         programStats.movelist);
5980                 SendToICS(buf);
5981 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5982           }
5983         }
5984 #endif
5985         /* currentMoveString is set as a side-effect of ParseOneMove */
5986         strcpy(machineMove, currentMoveString);
5987         strcat(machineMove, "\n");
5988         strcpy(moveList[forwardMostMove], machineMove);
5989
5990         /* [AS] Save move info and clear stats for next move */
5991         pvInfoList[ forwardMostMove ].score = programStats.score;
5992         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5993         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5994         ClearProgramStats();
5995         thinkOutput[0] = NULLCHAR;
5996         hiddenThinkOutputState = 0;
5997
5998         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5999
6000         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6001         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6002             int count = 0;
6003
6004             while( count < adjudicateLossPlies ) {
6005                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6006
6007                 if( count & 1 ) {
6008                     score = -score; /* Flip score for winning side */
6009                 }
6010
6011                 if( score > adjudicateLossThreshold ) {
6012                     break;
6013                 }
6014
6015                 count++;
6016             }
6017
6018             if( count >= adjudicateLossPlies ) {
6019                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6020
6021                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6022                     "Xboard adjudication", 
6023                     GE_XBOARD );
6024
6025                 return;
6026             }
6027         }
6028
6029         if( gameMode == TwoMachinesPlay ) {
6030           // [HGM] some adjudications useful with buggy engines
6031             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6032           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6033
6034
6035             if( appData.testLegality )
6036             {   /* [HGM] Some more adjudications for obstinate engines */
6037                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6038                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6039                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6040                 static int moveCount = 6;
6041                 ChessMove result;
6042                 char *reason = NULL;
6043
6044                 /* Count what is on board. */
6045                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6046                 {   ChessSquare p = boards[forwardMostMove][i][j];
6047                     int m=i;
6048
6049                     switch((int) p)
6050                     {   /* count B,N,R and other of each side */
6051                         case WhiteKing:
6052                         case BlackKing:
6053                              NrK++; break; // [HGM] atomic: count Kings
6054                         case WhiteKnight:
6055                              NrWN++; break;
6056                         case WhiteBishop:
6057                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6058                              bishopsColor |= 1 << ((i^j)&1);
6059                              NrWB++; break;
6060                         case BlackKnight:
6061                              NrBN++; break;
6062                         case BlackBishop:
6063                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6064                              bishopsColor |= 1 << ((i^j)&1);
6065                              NrBB++; break;
6066                         case WhiteRook:
6067                              NrWR++; break;
6068                         case BlackRook:
6069                              NrBR++; break;
6070                         case WhiteQueen:
6071                              NrWQ++; break;
6072                         case BlackQueen:
6073                              NrBQ++; break;
6074                         case EmptySquare: 
6075                              break;
6076                         case BlackPawn:
6077                              m = 7-i;
6078                         case WhitePawn:
6079                              PawnAdvance += m; NrPawns++;
6080                     }
6081                     NrPieces += (p != EmptySquare);
6082                     NrW += ((int)p < (int)BlackPawn);
6083                     if(gameInfo.variant == VariantXiangqi && 
6084                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6085                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6086                         NrW -= ((int)p < (int)BlackPawn);
6087                     }
6088                 }
6089
6090                 /* Some material-based adjudications that have to be made before stalemate test */
6091                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6092                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6093                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6094                      if(appData.checkMates) {
6095                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6096                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6097                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6098                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6099                          return;
6100                      }
6101                 }
6102
6103                 /* Bare King in Shatranj (loses) or Losers (wins) */
6104                 if( NrW == 1 || NrPieces - NrW == 1) {
6105                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6106                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6107                      if(appData.checkMates) {
6108                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6109                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6110                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6111                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6112                          return;
6113                      }
6114                   } else
6115                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6116                   {    /* bare King */
6117                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6118                         if(appData.checkMates) {
6119                             /* but only adjudicate if adjudication enabled */
6120                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6121                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6122                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6123                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6124                             return;
6125                         }
6126                   }
6127                 } else bare = 1;
6128
6129
6130             // don't wait for engine to announce game end if we can judge ourselves
6131             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6132                                        castlingRights[forwardMostMove]) ) {
6133               case MT_CHECK:
6134                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6135                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6136                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6137                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6138                             checkCnt++;
6139                         if(checkCnt >= 2) {
6140                             reason = "Xboard adjudication: 3rd check";
6141                             epStatus[forwardMostMove] = EP_CHECKMATE;
6142                             break;
6143                         }
6144                     }
6145                 }
6146               case MT_NONE:
6147               default:
6148                 break;
6149               case MT_STALEMATE:
6150               case MT_STAINMATE:
6151                 reason = "Xboard adjudication: Stalemate";
6152                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6153                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6154                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6155                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6156                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6157                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6158                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6159                                                                         EP_CHECKMATE : EP_WINS);
6160                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6161                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6162                 }
6163                 break;
6164               case MT_CHECKMATE:
6165                 reason = "Xboard adjudication: Checkmate";
6166                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6167                 break;
6168             }
6169
6170                 switch(i = epStatus[forwardMostMove]) {
6171                     case EP_STALEMATE:
6172                         result = GameIsDrawn; break;
6173                     case EP_CHECKMATE:
6174                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6175                     case EP_WINS:
6176                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6177                     default:
6178                         result = (ChessMove) 0;
6179                 }
6180                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6181                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6182                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6183                     GameEnds( result, reason, GE_XBOARD );
6184                     return;
6185                 }
6186
6187                 /* Next absolutely insufficient mating material. */
6188                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6189                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6190                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6191                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6192                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6193
6194                      /* always flag draws, for judging claims */
6195                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6196
6197                      if(appData.materialDraws) {
6198                          /* but only adjudicate them if adjudication enabled */
6199                          SendToProgram("force\n", cps->other); // suppress reply
6200                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6201                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6202                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6203                          return;
6204                      }
6205                 }
6206
6207                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6208                 if(NrPieces == 4 && 
6209                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6210                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6211                    || NrWN==2 || NrBN==2     /* KNNK */
6212                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6213                   ) ) {
6214                      if(--moveCount < 0 && appData.trivialDraws)
6215                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6216                           SendToProgram("force\n", cps->other); // suppress reply
6217                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6218                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6219                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6220                           return;
6221                      }
6222                 } else moveCount = 6;
6223             }
6224           }
6225           
6226           if (appData.debugMode) { int i;
6227             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6228                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6229                     appData.drawRepeats);
6230             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6231               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6232             
6233           }
6234
6235                 /* Check for rep-draws */
6236                 count = 0;
6237                 for(k = forwardMostMove-2;
6238                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6239                         epStatus[k] < EP_UNKNOWN &&
6240                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6241                     k-=2)
6242                 {   int rights=0;
6243                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6244                         /* compare castling rights */
6245                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6246                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6247                                 rights++; /* King lost rights, while rook still had them */
6248                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6249                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6250                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6251                                    rights++; /* but at least one rook lost them */
6252                         }
6253                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6254                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6255                                 rights++; 
6256                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6257                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6258                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6259                                    rights++;
6260                         }
6261                         if( rights == 0 && ++count > appData.drawRepeats-2
6262                             && appData.drawRepeats > 1) {
6263                              /* adjudicate after user-specified nr of repeats */
6264                              SendToProgram("force\n", cps->other); // suppress reply
6265                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6266                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6267                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6268                                 // [HGM] xiangqi: check for forbidden perpetuals
6269                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6270                                 for(m=forwardMostMove; m>k; m-=2) {
6271                                     if(MateTest(boards[m], PosFlags(m), 
6272                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6273                                         ourPerpetual = 0; // the current mover did not always check
6274                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6275                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6276                                         hisPerpetual = 0; // the opponent did not always check
6277                                 }
6278                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6279                                                                         ourPerpetual, hisPerpetual);
6280                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6281                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6282                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6283                                     return;
6284                                 }
6285                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6286                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6287                                 // Now check for perpetual chases
6288                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6289                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6290                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6291                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6292                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6293                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6294                                         return;
6295                                     }
6296                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6297                                         break; // Abort repetition-checking loop.
6298                                 }
6299                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6300                              }
6301                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6302                              return;
6303                         }
6304                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6305                              epStatus[forwardMostMove] = EP_REP_DRAW;
6306                     }
6307                 }
6308
6309                 /* Now we test for 50-move draws. Determine ply count */
6310                 count = forwardMostMove;
6311                 /* look for last irreversble move */
6312                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6313                     count--;
6314                 /* if we hit starting position, add initial plies */
6315                 if( count == backwardMostMove )
6316                     count -= initialRulePlies;
6317                 count = forwardMostMove - count; 
6318                 if( count >= 100)
6319                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6320                          /* this is used to judge if draw claims are legal */
6321                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6322                          SendToProgram("force\n", cps->other); // suppress reply
6323                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6324                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6325                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6326                          return;
6327                 }
6328
6329                 /* if draw offer is pending, treat it as a draw claim
6330                  * when draw condition present, to allow engines a way to
6331                  * claim draws before making their move to avoid a race
6332                  * condition occurring after their move
6333                  */
6334                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6335                          char *p = NULL;
6336                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6337                              p = "Draw claim: 50-move rule";
6338                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6339                              p = "Draw claim: 3-fold repetition";
6340                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6341                              p = "Draw claim: insufficient mating material";
6342                          if( p != NULL ) {
6343                              SendToProgram("force\n", cps->other); // suppress reply
6344                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6345                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6346                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6347                              return;
6348                          }
6349                 }
6350
6351
6352                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6353                     SendToProgram("force\n", cps->other); // suppress reply
6354                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6355                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6356
6357                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6358
6359                     return;
6360                 }
6361         }
6362
6363         bookHit = NULL;
6364         if (gameMode == TwoMachinesPlay) {
6365             /* [HGM] relaying draw offers moved to after reception of move */
6366             /* and interpreting offer as claim if it brings draw condition */
6367             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6368                 SendToProgram("draw\n", cps->other);
6369             }
6370             if (cps->other->sendTime) {
6371                 SendTimeRemaining(cps->other,
6372                                   cps->other->twoMachinesColor[0] == 'w');
6373             }
6374             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6375             if (firstMove && !bookHit) {
6376                 firstMove = FALSE;
6377                 if (cps->other->useColors) {
6378                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6379                 }
6380                 SendToProgram("go\n", cps->other);
6381             }
6382             cps->other->maybeThinking = TRUE;
6383         }
6384
6385         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6386         
6387         if (!pausing && appData.ringBellAfterMoves) {
6388             RingBell();
6389         }
6390
6391         /* 
6392          * Reenable menu items that were disabled while
6393          * machine was thinking
6394          */
6395         if (gameMode != TwoMachinesPlay)
6396             SetUserThinkingEnables();
6397
6398         // [HGM] book: after book hit opponent has received move and is now in force mode
6399         // force the book reply into it, and then fake that it outputted this move by jumping
6400         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6401         if(bookHit) {
6402                 static char bookMove[MSG_SIZ]; // a bit generous?
6403
6404                 strcpy(bookMove, "move ");
6405                 strcat(bookMove, bookHit);
6406                 message = bookMove;
6407                 cps = cps->other;
6408                 programStats.nodes = programStats.depth = programStats.time = 
6409                 programStats.score = programStats.got_only_move = 0;
6410                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6411
6412                 if(cps->lastPing != cps->lastPong) {
6413                     savedMessage = message; // args for deferred call
6414                     savedState = cps;
6415                     ScheduleDelayedEvent(DeferredBookMove, 10);
6416                     return;
6417                 }
6418                 goto FakeBookMove;
6419         }
6420
6421         return;
6422     }
6423
6424     /* Set special modes for chess engines.  Later something general
6425      *  could be added here; for now there is just one kludge feature,
6426      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6427      *  when "xboard" is given as an interactive command.
6428      */
6429     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6430         cps->useSigint = FALSE;
6431         cps->useSigterm = FALSE;
6432     }
6433     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6434       ParseFeatures(message+8, cps);
6435       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6436     }
6437
6438     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6439      * want this, I was asked to put it in, and obliged.
6440      */
6441     if (!strncmp(message, "setboard ", 9)) {
6442         Board initial_position; int i;
6443
6444         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6445
6446         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6447             DisplayError(_("Bad FEN received from engine"), 0);
6448             return ;
6449         } else {
6450            Reset(TRUE, FALSE);
6451            CopyBoard(boards[0], initial_position);
6452            initialRulePlies = FENrulePlies;
6453            epStatus[0] = FENepStatus;
6454            for( i=0; i<nrCastlingRights; i++ )
6455                 castlingRights[0][i] = FENcastlingRights[i];
6456            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6457            else gameMode = MachinePlaysBlack;                 
6458            DrawPosition(FALSE, boards[currentMove]);
6459         }
6460         return;
6461     }
6462
6463     /*
6464      * Look for communication commands
6465      */
6466     if (!strncmp(message, "telluser ", 9)) {
6467         DisplayNote(message + 9);
6468         return;
6469     }
6470     if (!strncmp(message, "tellusererror ", 14)) {
6471         DisplayError(message + 14, 0);
6472         return;
6473     }
6474     if (!strncmp(message, "tellopponent ", 13)) {
6475       if (appData.icsActive) {
6476         if (loggedOn) {
6477           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6478           SendToICS(buf1);
6479         }
6480       } else {
6481         DisplayNote(message + 13);
6482       }
6483       return;
6484     }
6485     if (!strncmp(message, "tellothers ", 11)) {
6486       if (appData.icsActive) {
6487         if (loggedOn) {
6488           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6489           SendToICS(buf1);
6490         }
6491       }
6492       return;
6493     }
6494     if (!strncmp(message, "tellall ", 8)) {
6495       if (appData.icsActive) {
6496         if (loggedOn) {
6497           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6498           SendToICS(buf1);
6499         }
6500       } else {
6501         DisplayNote(message + 8);
6502       }
6503       return;
6504     }
6505     if (strncmp(message, "warning", 7) == 0) {
6506         /* Undocumented feature, use tellusererror in new code */
6507         DisplayError(message, 0);
6508         return;
6509     }
6510     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6511         strcpy(realname, cps->tidy);
6512         strcat(realname, " query");
6513         AskQuestion(realname, buf2, buf1, cps->pr);
6514         return;
6515     }
6516     /* Commands from the engine directly to ICS.  We don't allow these to be 
6517      *  sent until we are logged on. Crafty kibitzes have been known to 
6518      *  interfere with the login process.
6519      */
6520     if (loggedOn) {
6521         if (!strncmp(message, "tellics ", 8)) {
6522             SendToICS(message + 8);
6523             SendToICS("\n");
6524             return;
6525         }
6526         if (!strncmp(message, "tellicsnoalias ", 15)) {
6527             SendToICS(ics_prefix);
6528             SendToICS(message + 15);
6529             SendToICS("\n");
6530             return;
6531         }
6532         /* The following are for backward compatibility only */
6533         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6534             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6535             SendToICS(ics_prefix);
6536             SendToICS(message);
6537             SendToICS("\n");
6538             return;
6539         }
6540     }
6541     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6542         return;
6543     }
6544     /*
6545      * If the move is illegal, cancel it and redraw the board.
6546      * Also deal with other error cases.  Matching is rather loose
6547      * here to accommodate engines written before the spec.
6548      */
6549     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6550         strncmp(message, "Error", 5) == 0) {
6551         if (StrStr(message, "name") || 
6552             StrStr(message, "rating") || StrStr(message, "?") ||
6553             StrStr(message, "result") || StrStr(message, "board") ||
6554             StrStr(message, "bk") || StrStr(message, "computer") ||
6555             StrStr(message, "variant") || StrStr(message, "hint") ||
6556             StrStr(message, "random") || StrStr(message, "depth") ||
6557             StrStr(message, "accepted")) {
6558             return;
6559         }
6560         if (StrStr(message, "protover")) {
6561           /* Program is responding to input, so it's apparently done
6562              initializing, and this error message indicates it is
6563              protocol version 1.  So we don't need to wait any longer
6564              for it to initialize and send feature commands. */
6565           FeatureDone(cps, 1);
6566           cps->protocolVersion = 1;
6567           return;
6568         }
6569         cps->maybeThinking = FALSE;
6570
6571         if (StrStr(message, "draw")) {
6572             /* Program doesn't have "draw" command */
6573             cps->sendDrawOffers = 0;
6574             return;
6575         }
6576         if (cps->sendTime != 1 &&
6577             (StrStr(message, "time") || StrStr(message, "otim"))) {
6578           /* Program apparently doesn't have "time" or "otim" command */
6579           cps->sendTime = 0;
6580           return;
6581         }
6582         if (StrStr(message, "analyze")) {
6583             cps->analysisSupport = FALSE;
6584             cps->analyzing = FALSE;
6585             Reset(FALSE, TRUE);
6586             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6587             DisplayError(buf2, 0);
6588             return;
6589         }
6590         if (StrStr(message, "(no matching move)st")) {
6591           /* Special kludge for GNU Chess 4 only */
6592           cps->stKludge = TRUE;
6593           SendTimeControl(cps, movesPerSession, timeControl,
6594                           timeIncrement, appData.searchDepth,
6595                           searchTime);
6596           return;
6597         }
6598         if (StrStr(message, "(no matching move)sd")) {
6599           /* Special kludge for GNU Chess 4 only */
6600           cps->sdKludge = TRUE;
6601           SendTimeControl(cps, movesPerSession, timeControl,
6602                           timeIncrement, appData.searchDepth,
6603                           searchTime);
6604           return;
6605         }
6606         if (!StrStr(message, "llegal")) {
6607             return;
6608         }
6609         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6610             gameMode == IcsIdle) return;
6611         if (forwardMostMove <= backwardMostMove) return;
6612         if (pausing) PauseEvent();
6613       if(appData.forceIllegal) {
6614             // [HGM] illegal: machine refused move; force position after move into it
6615           SendToProgram("force\n", cps);
6616           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6617                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6618                 // when black is to move, while there might be nothing on a2 or black
6619                 // might already have the move. So send the board as if white has the move.
6620                 // But first we must change the stm of the engine, as it refused the last move
6621                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6622                 if(WhiteOnMove(forwardMostMove)) {
6623                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6624                     SendBoard(cps, forwardMostMove); // kludgeless board
6625                 } else {
6626                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6627                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6628                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6629                 }
6630           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6631             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6632                  gameMode == TwoMachinesPlay)
6633               SendToProgram("go\n", cps);
6634             return;
6635       } else
6636         if (gameMode == PlayFromGameFile) {
6637             /* Stop reading this game file */
6638             gameMode = EditGame;
6639             ModeHighlight();
6640         }
6641         currentMove = --forwardMostMove;
6642         DisplayMove(currentMove-1); /* before DisplayMoveError */
6643         SwitchClocks();
6644         DisplayBothClocks();
6645         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6646                 parseList[currentMove], cps->which);
6647         DisplayMoveError(buf1);
6648         DrawPosition(FALSE, boards[currentMove]);
6649
6650         /* [HGM] illegal-move claim should forfeit game when Xboard */
6651         /* only passes fully legal moves                            */
6652         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6653             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6654                                 "False illegal-move claim", GE_XBOARD );
6655         }
6656         return;
6657     }
6658     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6659         /* Program has a broken "time" command that
6660            outputs a string not ending in newline.
6661            Don't use it. */
6662         cps->sendTime = 0;
6663     }
6664     
6665     /*
6666      * If chess program startup fails, exit with an error message.
6667      * Attempts to recover here are futile.
6668      */
6669     if ((StrStr(message, "unknown host") != NULL)
6670         || (StrStr(message, "No remote directory") != NULL)
6671         || (StrStr(message, "not found") != NULL)
6672         || (StrStr(message, "No such file") != NULL)
6673         || (StrStr(message, "can't alloc") != NULL)
6674         || (StrStr(message, "Permission denied") != NULL)) {
6675
6676         cps->maybeThinking = FALSE;
6677         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6678                 cps->which, cps->program, cps->host, message);
6679         RemoveInputSource(cps->isr);
6680         DisplayFatalError(buf1, 0, 1);
6681         return;
6682     }
6683     
6684     /* 
6685      * Look for hint output
6686      */
6687     if (sscanf(message, "Hint: %s", buf1) == 1) {
6688         if (cps == &first && hintRequested) {
6689             hintRequested = FALSE;
6690             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6691                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6692                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6693                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6694                                     fromY, fromX, toY, toX, promoChar, buf1);
6695                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6696                 DisplayInformation(buf2);
6697             } else {
6698                 /* Hint move could not be parsed!? */
6699               snprintf(buf2, sizeof(buf2),
6700                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6701                         buf1, cps->which);
6702                 DisplayError(buf2, 0);
6703             }
6704         } else {
6705             strcpy(lastHint, buf1);
6706         }
6707         return;
6708     }
6709
6710     /*
6711      * Ignore other messages if game is not in progress
6712      */
6713     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6714         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6715
6716     /*
6717      * look for win, lose, draw, or draw offer
6718      */
6719     if (strncmp(message, "1-0", 3) == 0) {
6720         char *p, *q, *r = "";
6721         p = strchr(message, '{');
6722         if (p) {
6723             q = strchr(p, '}');
6724             if (q) {
6725                 *q = NULLCHAR;
6726                 r = p + 1;
6727             }
6728         }
6729         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6730         return;
6731     } else if (strncmp(message, "0-1", 3) == 0) {
6732         char *p, *q, *r = "";
6733         p = strchr(message, '{');
6734         if (p) {
6735             q = strchr(p, '}');
6736             if (q) {
6737                 *q = NULLCHAR;
6738                 r = p + 1;
6739             }
6740         }
6741         /* Kludge for Arasan 4.1 bug */
6742         if (strcmp(r, "Black resigns") == 0) {
6743             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6744             return;
6745         }
6746         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6747         return;
6748     } else if (strncmp(message, "1/2", 3) == 0) {
6749         char *p, *q, *r = "";
6750         p = strchr(message, '{');
6751         if (p) {
6752             q = strchr(p, '}');
6753             if (q) {
6754                 *q = NULLCHAR;
6755                 r = p + 1;
6756             }
6757         }
6758             
6759         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6760         return;
6761
6762     } else if (strncmp(message, "White resign", 12) == 0) {
6763         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6764         return;
6765     } else if (strncmp(message, "Black resign", 12) == 0) {
6766         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "White matches", 13) == 0 ||
6769                strncmp(message, "Black matches", 13) == 0   ) {
6770         /* [HGM] ignore GNUShogi noises */
6771         return;
6772     } else if (strncmp(message, "White", 5) == 0 &&
6773                message[5] != '(' &&
6774                StrStr(message, "Black") == NULL) {
6775         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6776         return;
6777     } else if (strncmp(message, "Black", 5) == 0 &&
6778                message[5] != '(') {
6779         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6780         return;
6781     } else if (strcmp(message, "resign") == 0 ||
6782                strcmp(message, "computer resigns") == 0) {
6783         switch (gameMode) {
6784           case MachinePlaysBlack:
6785           case IcsPlayingBlack:
6786             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6787             break;
6788           case MachinePlaysWhite:
6789           case IcsPlayingWhite:
6790             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6791             break;
6792           case TwoMachinesPlay:
6793             if (cps->twoMachinesColor[0] == 'w')
6794               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6795             else
6796               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6797             break;
6798           default:
6799             /* can't happen */
6800             break;
6801         }
6802         return;
6803     } else if (strncmp(message, "opponent mates", 14) == 0) {
6804         switch (gameMode) {
6805           case MachinePlaysBlack:
6806           case IcsPlayingBlack:
6807             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6808             break;
6809           case MachinePlaysWhite:
6810           case IcsPlayingWhite:
6811             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6812             break;
6813           case TwoMachinesPlay:
6814             if (cps->twoMachinesColor[0] == 'w')
6815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6816             else
6817               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6818             break;
6819           default:
6820             /* can't happen */
6821             break;
6822         }
6823         return;
6824     } else if (strncmp(message, "computer mates", 14) == 0) {
6825         switch (gameMode) {
6826           case MachinePlaysBlack:
6827           case IcsPlayingBlack:
6828             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6829             break;
6830           case MachinePlaysWhite:
6831           case IcsPlayingWhite:
6832             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6833             break;
6834           case TwoMachinesPlay:
6835             if (cps->twoMachinesColor[0] == 'w')
6836               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6837             else
6838               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839             break;
6840           default:
6841             /* can't happen */
6842             break;
6843         }
6844         return;
6845     } else if (strncmp(message, "checkmate", 9) == 0) {
6846         if (WhiteOnMove(forwardMostMove)) {
6847             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6848         } else {
6849             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6850         }
6851         return;
6852     } else if (strstr(message, "Draw") != NULL ||
6853                strstr(message, "game is a draw") != NULL) {
6854         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6855         return;
6856     } else if (strstr(message, "offer") != NULL &&
6857                strstr(message, "draw") != NULL) {
6858 #if ZIPPY
6859         if (appData.zippyPlay && first.initDone) {
6860             /* Relay offer to ICS */
6861             SendToICS(ics_prefix);
6862             SendToICS("draw\n");
6863         }
6864 #endif
6865         cps->offeredDraw = 2; /* valid until this engine moves twice */
6866         if (gameMode == TwoMachinesPlay) {
6867             if (cps->other->offeredDraw) {
6868                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6869             /* [HGM] in two-machine mode we delay relaying draw offer      */
6870             /* until after we also have move, to see if it is really claim */
6871             }
6872         } else if (gameMode == MachinePlaysWhite ||
6873                    gameMode == MachinePlaysBlack) {
6874           if (userOfferedDraw) {
6875             DisplayInformation(_("Machine accepts your draw offer"));
6876             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6877           } else {
6878             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6879           }
6880         }
6881     }
6882
6883     
6884     /*
6885      * Look for thinking output
6886      */
6887     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6888           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6889                                 ) {
6890         int plylev, mvleft, mvtot, curscore, time;
6891         char mvname[MOVE_LEN];
6892         u64 nodes; // [DM]
6893         char plyext;
6894         int ignore = FALSE;
6895         int prefixHint = FALSE;
6896         mvname[0] = NULLCHAR;
6897
6898         switch (gameMode) {
6899           case MachinePlaysBlack:
6900           case IcsPlayingBlack:
6901             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6902             break;
6903           case MachinePlaysWhite:
6904           case IcsPlayingWhite:
6905             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6906             break;
6907           case AnalyzeMode:
6908           case AnalyzeFile:
6909             break;
6910           case IcsObserving: /* [DM] icsEngineAnalyze */
6911             if (!appData.icsEngineAnalyze) ignore = TRUE;
6912             break;
6913           case TwoMachinesPlay:
6914             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6915                 ignore = TRUE;
6916             }
6917             break;
6918           default:
6919             ignore = TRUE;
6920             break;
6921         }
6922
6923         if (!ignore) {
6924             buf1[0] = NULLCHAR;
6925             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6926                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6927
6928                 if (plyext != ' ' && plyext != '\t') {
6929                     time *= 100;
6930                 }
6931
6932                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6933                 if( cps->scoreIsAbsolute && 
6934                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6935                 {
6936                     curscore = -curscore;
6937                 }
6938
6939
6940                 programStats.depth = plylev;
6941                 programStats.nodes = nodes;
6942                 programStats.time = time;
6943                 programStats.score = curscore;
6944                 programStats.got_only_move = 0;
6945
6946                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6947                         int ticklen;
6948
6949                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6950                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6951                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6952                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6953                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6954                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6955                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6956                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6957                 }
6958
6959                 /* Buffer overflow protection */
6960                 if (buf1[0] != NULLCHAR) {
6961                     if (strlen(buf1) >= sizeof(programStats.movelist)
6962                         && appData.debugMode) {
6963                         fprintf(debugFP,
6964                                 "PV is too long; using the first %u bytes.\n",
6965                                 (unsigned) sizeof(programStats.movelist) - 1);
6966                     }
6967
6968                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6969                 } else {
6970                     sprintf(programStats.movelist, " no PV\n");
6971                 }
6972
6973                 if (programStats.seen_stat) {
6974                     programStats.ok_to_send = 1;
6975                 }
6976
6977                 if (strchr(programStats.movelist, '(') != NULL) {
6978                     programStats.line_is_book = 1;
6979                     programStats.nr_moves = 0;
6980                     programStats.moves_left = 0;
6981                 } else {
6982                     programStats.line_is_book = 0;
6983                 }
6984
6985                 SendProgramStatsToFrontend( cps, &programStats );
6986
6987                 /* 
6988                     [AS] Protect the thinkOutput buffer from overflow... this
6989                     is only useful if buf1 hasn't overflowed first!
6990                 */
6991                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6992                         plylev, 
6993                         (gameMode == TwoMachinesPlay ?
6994                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6995                         ((double) curscore) / 100.0,
6996                         prefixHint ? lastHint : "",
6997                         prefixHint ? " " : "" );
6998
6999                 if( buf1[0] != NULLCHAR ) {
7000                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7001
7002                     if( strlen(buf1) > max_len ) {
7003                         if( appData.debugMode) {
7004                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7005                         }
7006                         buf1[max_len+1] = '\0';
7007                     }
7008
7009                     strcat( thinkOutput, buf1 );
7010                 }
7011
7012                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7013                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7014                     DisplayMove(currentMove - 1);
7015                 }
7016                 return;
7017
7018             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7019                 /* crafty (9.25+) says "(only move) <move>"
7020                  * if there is only 1 legal move
7021                  */
7022                 sscanf(p, "(only move) %s", buf1);
7023                 sprintf(thinkOutput, "%s (only move)", buf1);
7024                 sprintf(programStats.movelist, "%s (only move)", buf1);
7025                 programStats.depth = 1;
7026                 programStats.nr_moves = 1;
7027                 programStats.moves_left = 1;
7028                 programStats.nodes = 1;
7029                 programStats.time = 1;
7030                 programStats.got_only_move = 1;
7031
7032                 /* Not really, but we also use this member to
7033                    mean "line isn't going to change" (Crafty
7034                    isn't searching, so stats won't change) */
7035                 programStats.line_is_book = 1;
7036
7037                 SendProgramStatsToFrontend( cps, &programStats );
7038                 
7039                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7040                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7041                     DisplayMove(currentMove - 1);
7042                 }
7043                 return;
7044             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7045                               &time, &nodes, &plylev, &mvleft,
7046                               &mvtot, mvname) >= 5) {
7047                 /* The stat01: line is from Crafty (9.29+) in response
7048                    to the "." command */
7049                 programStats.seen_stat = 1;
7050                 cps->maybeThinking = TRUE;
7051
7052                 if (programStats.got_only_move || !appData.periodicUpdates)
7053                   return;
7054
7055                 programStats.depth = plylev;
7056                 programStats.time = time;
7057                 programStats.nodes = nodes;
7058                 programStats.moves_left = mvleft;
7059                 programStats.nr_moves = mvtot;
7060                 strcpy(programStats.move_name, mvname);
7061                 programStats.ok_to_send = 1;
7062                 programStats.movelist[0] = '\0';
7063
7064                 SendProgramStatsToFrontend( cps, &programStats );
7065
7066                 return;
7067
7068             } else if (strncmp(message,"++",2) == 0) {
7069                 /* Crafty 9.29+ outputs this */
7070                 programStats.got_fail = 2;
7071                 return;
7072
7073             } else if (strncmp(message,"--",2) == 0) {
7074                 /* Crafty 9.29+ outputs this */
7075                 programStats.got_fail = 1;
7076                 return;
7077
7078             } else if (thinkOutput[0] != NULLCHAR &&
7079                        strncmp(message, "    ", 4) == 0) {
7080                 unsigned message_len;
7081
7082                 p = message;
7083                 while (*p && *p == ' ') p++;
7084
7085                 message_len = strlen( p );
7086
7087                 /* [AS] Avoid buffer overflow */
7088                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7089                     strcat(thinkOutput, " ");
7090                     strcat(thinkOutput, p);
7091                 }
7092
7093                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7094                     strcat(programStats.movelist, " ");
7095                     strcat(programStats.movelist, p);
7096                 }
7097
7098                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7099                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7100                     DisplayMove(currentMove - 1);
7101                 }
7102                 return;
7103             }
7104         }
7105         else {
7106             buf1[0] = NULLCHAR;
7107
7108             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7109                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7110             {
7111                 ChessProgramStats cpstats;
7112
7113                 if (plyext != ' ' && plyext != '\t') {
7114                     time *= 100;
7115                 }
7116
7117                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7118                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7119                     curscore = -curscore;
7120                 }
7121
7122                 cpstats.depth = plylev;
7123                 cpstats.nodes = nodes;
7124                 cpstats.time = time;
7125                 cpstats.score = curscore;
7126                 cpstats.got_only_move = 0;
7127                 cpstats.movelist[0] = '\0';
7128
7129                 if (buf1[0] != NULLCHAR) {
7130                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7131                 }
7132
7133                 cpstats.ok_to_send = 0;
7134                 cpstats.line_is_book = 0;
7135                 cpstats.nr_moves = 0;
7136                 cpstats.moves_left = 0;
7137
7138                 SendProgramStatsToFrontend( cps, &cpstats );
7139             }
7140         }
7141     }
7142 }
7143
7144
7145 /* Parse a game score from the character string "game", and
7146    record it as the history of the current game.  The game
7147    score is NOT assumed to start from the standard position. 
7148    The display is not updated in any way.
7149    */
7150 void
7151 ParseGameHistory(game)
7152      char *game;
7153 {
7154     ChessMove moveType;
7155     int fromX, fromY, toX, toY, boardIndex;
7156     char promoChar;
7157     char *p, *q;
7158     char buf[MSG_SIZ];
7159
7160     if (appData.debugMode)
7161       fprintf(debugFP, "Parsing game history: %s\n", game);
7162
7163     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7164     gameInfo.site = StrSave(appData.icsHost);
7165     gameInfo.date = PGNDate();
7166     gameInfo.round = StrSave("-");
7167
7168     /* Parse out names of players */
7169     while (*game == ' ') game++;
7170     p = buf;
7171     while (*game != ' ') *p++ = *game++;
7172     *p = NULLCHAR;
7173     gameInfo.white = StrSave(buf);
7174     while (*game == ' ') game++;
7175     p = buf;
7176     while (*game != ' ' && *game != '\n') *p++ = *game++;
7177     *p = NULLCHAR;
7178     gameInfo.black = StrSave(buf);
7179
7180     /* Parse moves */
7181     boardIndex = blackPlaysFirst ? 1 : 0;
7182     yynewstr(game);
7183     for (;;) {
7184         yyboardindex = boardIndex;
7185         moveType = (ChessMove) yylex();
7186         switch (moveType) {
7187           case IllegalMove:             /* maybe suicide chess, etc. */
7188   if (appData.debugMode) {
7189     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7190     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7191     setbuf(debugFP, NULL);
7192   }
7193           case WhitePromotionChancellor:
7194           case BlackPromotionChancellor:
7195           case WhitePromotionArchbishop:
7196           case BlackPromotionArchbishop:
7197           case WhitePromotionQueen:
7198           case BlackPromotionQueen:
7199           case WhitePromotionRook:
7200           case BlackPromotionRook:
7201           case WhitePromotionBishop:
7202           case BlackPromotionBishop:
7203           case WhitePromotionKnight:
7204           case BlackPromotionKnight:
7205           case WhitePromotionKing:
7206           case BlackPromotionKing:
7207           case NormalMove:
7208           case WhiteCapturesEnPassant:
7209           case BlackCapturesEnPassant:
7210           case WhiteKingSideCastle:
7211           case WhiteQueenSideCastle:
7212           case BlackKingSideCastle:
7213           case BlackQueenSideCastle:
7214           case WhiteKingSideCastleWild:
7215           case WhiteQueenSideCastleWild:
7216           case BlackKingSideCastleWild:
7217           case BlackQueenSideCastleWild:
7218           /* PUSH Fabien */
7219           case WhiteHSideCastleFR:
7220           case WhiteASideCastleFR:
7221           case BlackHSideCastleFR:
7222           case BlackASideCastleFR:
7223           /* POP Fabien */
7224             fromX = currentMoveString[0] - AAA;
7225             fromY = currentMoveString[1] - ONE;
7226             toX = currentMoveString[2] - AAA;
7227             toY = currentMoveString[3] - ONE;
7228             promoChar = currentMoveString[4];
7229             break;
7230           case WhiteDrop:
7231           case BlackDrop:
7232             fromX = moveType == WhiteDrop ?
7233               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7234             (int) CharToPiece(ToLower(currentMoveString[0]));
7235             fromY = DROP_RANK;
7236             toX = currentMoveString[2] - AAA;
7237             toY = currentMoveString[3] - ONE;
7238             promoChar = NULLCHAR;
7239             break;
7240           case AmbiguousMove:
7241             /* bug? */
7242             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7243   if (appData.debugMode) {
7244     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7246     setbuf(debugFP, NULL);
7247   }
7248             DisplayError(buf, 0);
7249             return;
7250           case ImpossibleMove:
7251             /* bug? */
7252             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7253   if (appData.debugMode) {
7254     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7255     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7256     setbuf(debugFP, NULL);
7257   }
7258             DisplayError(buf, 0);
7259             return;
7260           case (ChessMove) 0:   /* end of file */
7261             if (boardIndex < backwardMostMove) {
7262                 /* Oops, gap.  How did that happen? */
7263                 DisplayError(_("Gap in move list"), 0);
7264                 return;
7265             }
7266             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7267             if (boardIndex > forwardMostMove) {
7268                 forwardMostMove = boardIndex;
7269             }
7270             return;
7271           case ElapsedTime:
7272             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7273                 strcat(parseList[boardIndex-1], " ");
7274                 strcat(parseList[boardIndex-1], yy_text);
7275             }
7276             continue;
7277           case Comment:
7278           case PGNTag:
7279           case NAG:
7280           default:
7281             /* ignore */
7282             continue;
7283           case WhiteWins:
7284           case BlackWins:
7285           case GameIsDrawn:
7286           case GameUnfinished:
7287             if (gameMode == IcsExamining) {
7288                 if (boardIndex < backwardMostMove) {
7289                     /* Oops, gap.  How did that happen? */
7290                     return;
7291                 }
7292                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7293                 return;
7294             }
7295             gameInfo.result = moveType;
7296             p = strchr(yy_text, '{');
7297             if (p == NULL) p = strchr(yy_text, '(');
7298             if (p == NULL) {
7299                 p = yy_text;
7300                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7301             } else {
7302                 q = strchr(p, *p == '{' ? '}' : ')');
7303                 if (q != NULL) *q = NULLCHAR;
7304                 p++;
7305             }
7306             gameInfo.resultDetails = StrSave(p);
7307             continue;
7308         }
7309         if (boardIndex >= forwardMostMove &&
7310             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7311             backwardMostMove = blackPlaysFirst ? 1 : 0;
7312             return;
7313         }
7314         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7315                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7316                                  parseList[boardIndex]);
7317         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7318         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7319         /* currentMoveString is set as a side-effect of yylex */
7320         strcpy(moveList[boardIndex], currentMoveString);
7321         strcat(moveList[boardIndex], "\n");
7322         boardIndex++;
7323         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7324                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7325         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7326                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7327           case MT_NONE:
7328           case MT_STALEMATE:
7329           default:
7330             break;
7331           case MT_CHECK:
7332             if(gameInfo.variant != VariantShogi)
7333                 strcat(parseList[boardIndex - 1], "+");
7334             break;
7335           case MT_CHECKMATE:
7336           case MT_STAINMATE:
7337             strcat(parseList[boardIndex - 1], "#");
7338             break;
7339         }
7340     }
7341 }
7342
7343
7344 /* Apply a move to the given board  */
7345 void
7346 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7347      int fromX, fromY, toX, toY;
7348      int promoChar;
7349      Board board;
7350      char *castling;
7351      char *ep;
7352 {
7353   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7354
7355     /* [HGM] compute & store e.p. status and castling rights for new position */
7356     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7357     { int i;
7358
7359       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7360       oldEP = *ep;
7361       *ep = EP_NONE;
7362
7363       if( board[toY][toX] != EmptySquare ) 
7364            *ep = EP_CAPTURE;  
7365
7366       if( board[fromY][fromX] == WhitePawn ) {
7367            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7368                *ep = EP_PAWN_MOVE;
7369            if( toY-fromY==2) {
7370                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7371                         gameInfo.variant != VariantBerolina || toX < fromX)
7372                       *ep = toX | berolina;
7373                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7374                         gameInfo.variant != VariantBerolina || toX > fromX) 
7375                       *ep = toX;
7376            }
7377       } else 
7378       if( board[fromY][fromX] == BlackPawn ) {
7379            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7380                *ep = EP_PAWN_MOVE; 
7381            if( toY-fromY== -2) {
7382                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7383                         gameInfo.variant != VariantBerolina || toX < fromX)
7384                       *ep = toX | berolina;
7385                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7386                         gameInfo.variant != VariantBerolina || toX > fromX) 
7387                       *ep = toX;
7388            }
7389        }
7390
7391        for(i=0; i<nrCastlingRights; i++) {
7392            if(castling[i] == fromX && castlingRank[i] == fromY ||
7393               castling[i] == toX   && castlingRank[i] == toY   
7394              ) castling[i] = -1; // revoke for moved or captured piece
7395        }
7396
7397     }
7398
7399   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7400   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7401        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7402          
7403   if (fromX == toX && fromY == toY) return;
7404
7405   if (fromY == DROP_RANK) {
7406         /* must be first */
7407         piece = board[toY][toX] = (ChessSquare) fromX;
7408   } else {
7409      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7410      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7411      if(gameInfo.variant == VariantKnightmate)
7412          king += (int) WhiteUnicorn - (int) WhiteKing;
7413
7414     /* Code added by Tord: */
7415     /* FRC castling assumed when king captures friendly rook. */
7416     if (board[fromY][fromX] == WhiteKing &&
7417              board[toY][toX] == WhiteRook) {
7418       board[fromY][fromX] = EmptySquare;
7419       board[toY][toX] = EmptySquare;
7420       if(toX > fromX) {
7421         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7422       } else {
7423         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7424       }
7425     } else if (board[fromY][fromX] == BlackKing &&
7426                board[toY][toX] == BlackRook) {
7427       board[fromY][fromX] = EmptySquare;
7428       board[toY][toX] = EmptySquare;
7429       if(toX > fromX) {
7430         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7431       } else {
7432         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7433       }
7434     /* End of code added by Tord */
7435
7436     } else if (board[fromY][fromX] == king
7437         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7438         && toY == fromY && toX > fromX+1) {
7439         board[fromY][fromX] = EmptySquare;
7440         board[toY][toX] = king;
7441         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7442         board[fromY][BOARD_RGHT-1] = EmptySquare;
7443     } else if (board[fromY][fromX] == king
7444         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7445                && toY == fromY && toX < fromX-1) {
7446         board[fromY][fromX] = EmptySquare;
7447         board[toY][toX] = king;
7448         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7449         board[fromY][BOARD_LEFT] = EmptySquare;
7450     } else if (board[fromY][fromX] == WhitePawn
7451                && toY == BOARD_HEIGHT-1
7452                && gameInfo.variant != VariantXiangqi
7453                ) {
7454         /* white pawn promotion */
7455         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7456         if (board[toY][toX] == EmptySquare) {
7457             board[toY][toX] = WhiteQueen;
7458         }
7459         if(gameInfo.variant==VariantBughouse ||
7460            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7461             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7462         board[fromY][fromX] = EmptySquare;
7463     } else if ((fromY == BOARD_HEIGHT-4)
7464                && (toX != fromX)
7465                && gameInfo.variant != VariantXiangqi
7466                && gameInfo.variant != VariantBerolina
7467                && (board[fromY][fromX] == WhitePawn)
7468                && (board[toY][toX] == EmptySquare)) {
7469         board[fromY][fromX] = EmptySquare;
7470         board[toY][toX] = WhitePawn;
7471         captured = board[toY - 1][toX];
7472         board[toY - 1][toX] = EmptySquare;
7473     } else if ((fromY == BOARD_HEIGHT-4)
7474                && (toX == fromX)
7475                && gameInfo.variant == VariantBerolina
7476                && (board[fromY][fromX] == WhitePawn)
7477                && (board[toY][toX] == EmptySquare)) {
7478         board[fromY][fromX] = EmptySquare;
7479         board[toY][toX] = WhitePawn;
7480         if(oldEP & EP_BEROLIN_A) {
7481                 captured = board[fromY][fromX-1];
7482                 board[fromY][fromX-1] = EmptySquare;
7483         }else{  captured = board[fromY][fromX+1];
7484                 board[fromY][fromX+1] = EmptySquare;
7485         }
7486     } else if (board[fromY][fromX] == king
7487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488                && toY == fromY && toX > fromX+1) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = king;
7491         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7492         board[fromY][BOARD_RGHT-1] = EmptySquare;
7493     } else if (board[fromY][fromX] == king
7494         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7495                && toY == fromY && toX < fromX-1) {
7496         board[fromY][fromX] = EmptySquare;
7497         board[toY][toX] = king;
7498         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7499         board[fromY][BOARD_LEFT] = EmptySquare;
7500     } else if (fromY == 7 && fromX == 3
7501                && board[fromY][fromX] == BlackKing
7502                && toY == 7 && toX == 5) {
7503         board[fromY][fromX] = EmptySquare;
7504         board[toY][toX] = BlackKing;
7505         board[fromY][7] = EmptySquare;
7506         board[toY][4] = BlackRook;
7507     } else if (fromY == 7 && fromX == 3
7508                && board[fromY][fromX] == BlackKing
7509                && toY == 7 && toX == 1) {
7510         board[fromY][fromX] = EmptySquare;
7511         board[toY][toX] = BlackKing;
7512         board[fromY][0] = EmptySquare;
7513         board[toY][2] = BlackRook;
7514     } else if (board[fromY][fromX] == BlackPawn
7515                && toY == 0
7516                && gameInfo.variant != VariantXiangqi
7517                ) {
7518         /* black pawn promotion */
7519         board[0][toX] = CharToPiece(ToLower(promoChar));
7520         if (board[0][toX] == EmptySquare) {
7521             board[0][toX] = BlackQueen;
7522         }
7523         if(gameInfo.variant==VariantBughouse ||
7524            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7525             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7526         board[fromY][fromX] = EmptySquare;
7527     } else if ((fromY == 3)
7528                && (toX != fromX)
7529                && gameInfo.variant != VariantXiangqi
7530                && gameInfo.variant != VariantBerolina
7531                && (board[fromY][fromX] == BlackPawn)
7532                && (board[toY][toX] == EmptySquare)) {
7533         board[fromY][fromX] = EmptySquare;
7534         board[toY][toX] = BlackPawn;
7535         captured = board[toY + 1][toX];
7536         board[toY + 1][toX] = EmptySquare;
7537     } else if ((fromY == 3)
7538                && (toX == fromX)
7539                && gameInfo.variant == VariantBerolina
7540                && (board[fromY][fromX] == BlackPawn)
7541                && (board[toY][toX] == EmptySquare)) {
7542         board[fromY][fromX] = EmptySquare;
7543         board[toY][toX] = BlackPawn;
7544         if(oldEP & EP_BEROLIN_A) {
7545                 captured = board[fromY][fromX-1];
7546                 board[fromY][fromX-1] = EmptySquare;
7547         }else{  captured = board[fromY][fromX+1];
7548                 board[fromY][fromX+1] = EmptySquare;
7549         }
7550     } else {
7551         board[toY][toX] = board[fromY][fromX];
7552         board[fromY][fromX] = EmptySquare;
7553     }
7554
7555     /* [HGM] now we promote for Shogi, if needed */
7556     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7557         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7558   }
7559
7560     if (gameInfo.holdingsWidth != 0) {
7561
7562       /* !!A lot more code needs to be written to support holdings  */
7563       /* [HGM] OK, so I have written it. Holdings are stored in the */
7564       /* penultimate board files, so they are automaticlly stored   */
7565       /* in the game history.                                       */
7566       if (fromY == DROP_RANK) {
7567         /* Delete from holdings, by decreasing count */
7568         /* and erasing image if necessary            */
7569         p = (int) fromX;
7570         if(p < (int) BlackPawn) { /* white drop */
7571              p -= (int)WhitePawn;
7572                  p = PieceToNumber((ChessSquare)p);
7573              if(p >= gameInfo.holdingsSize) p = 0;
7574              if(--board[p][BOARD_WIDTH-2] <= 0)
7575                   board[p][BOARD_WIDTH-1] = EmptySquare;
7576              if((int)board[p][BOARD_WIDTH-2] < 0)
7577                         board[p][BOARD_WIDTH-2] = 0;
7578         } else {                  /* black drop */
7579              p -= (int)BlackPawn;
7580                  p = PieceToNumber((ChessSquare)p);
7581              if(p >= gameInfo.holdingsSize) p = 0;
7582              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7583                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7584              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7585                         board[BOARD_HEIGHT-1-p][1] = 0;
7586         }
7587       }
7588       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7589           && gameInfo.variant != VariantBughouse        ) {
7590         /* [HGM] holdings: Add to holdings, if holdings exist */
7591         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7592                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7593                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7594         }
7595         p = (int) captured;
7596         if (p >= (int) BlackPawn) {
7597           p -= (int)BlackPawn;
7598           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7599                   /* in Shogi restore piece to its original  first */
7600                   captured = (ChessSquare) (DEMOTED captured);
7601                   p = DEMOTED p;
7602           }
7603           p = PieceToNumber((ChessSquare)p);
7604           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7605           board[p][BOARD_WIDTH-2]++;
7606           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7607         } else {
7608           p -= (int)WhitePawn;
7609           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7610                   captured = (ChessSquare) (DEMOTED captured);
7611                   p = DEMOTED p;
7612           }
7613           p = PieceToNumber((ChessSquare)p);
7614           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7615           board[BOARD_HEIGHT-1-p][1]++;
7616           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7617         }
7618       }
7619     } else if (gameInfo.variant == VariantAtomic) {
7620       if (captured != EmptySquare) {
7621         int y, x;
7622         for (y = toY-1; y <= toY+1; y++) {
7623           for (x = toX-1; x <= toX+1; x++) {
7624             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7625                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7626               board[y][x] = EmptySquare;
7627             }
7628           }
7629         }
7630         board[toY][toX] = EmptySquare;
7631       }
7632     }
7633     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7634         /* [HGM] Shogi promotions */
7635         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7636     }
7637
7638     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7639                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7640         // [HGM] superchess: take promotion piece out of holdings
7641         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7642         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7643             if(!--board[k][BOARD_WIDTH-2])
7644                 board[k][BOARD_WIDTH-1] = EmptySquare;
7645         } else {
7646             if(!--board[BOARD_HEIGHT-1-k][1])
7647                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7648         }
7649     }
7650
7651 }
7652
7653 /* Updates forwardMostMove */
7654 void
7655 MakeMove(fromX, fromY, toX, toY, promoChar)
7656      int fromX, fromY, toX, toY;
7657      int promoChar;
7658 {
7659 //    forwardMostMove++; // [HGM] bare: moved downstream
7660
7661     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7662         int timeLeft; static int lastLoadFlag=0; int king, piece;
7663         piece = boards[forwardMostMove][fromY][fromX];
7664         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7665         if(gameInfo.variant == VariantKnightmate)
7666             king += (int) WhiteUnicorn - (int) WhiteKing;
7667         if(forwardMostMove == 0) {
7668             if(blackPlaysFirst) 
7669                 fprintf(serverMoves, "%s;", second.tidy);
7670             fprintf(serverMoves, "%s;", first.tidy);
7671             if(!blackPlaysFirst) 
7672                 fprintf(serverMoves, "%s;", second.tidy);
7673         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7674         lastLoadFlag = loadFlag;
7675         // print base move
7676         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7677         // print castling suffix
7678         if( toY == fromY && piece == king ) {
7679             if(toX-fromX > 1)
7680                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7681             if(fromX-toX >1)
7682                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7683         }
7684         // e.p. suffix
7685         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7686              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7687              boards[forwardMostMove][toY][toX] == EmptySquare
7688              && fromX != toX )
7689                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7690         // promotion suffix
7691         if(promoChar != NULLCHAR)
7692                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7693         if(!loadFlag) {
7694             fprintf(serverMoves, "/%d/%d",
7695                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7696             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7697             else                      timeLeft = blackTimeRemaining/1000;
7698             fprintf(serverMoves, "/%d", timeLeft);
7699         }
7700         fflush(serverMoves);
7701     }
7702
7703     if (forwardMostMove+1 >= MAX_MOVES) {
7704       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7705                         0, 1);
7706       return;
7707     }
7708     if (commentList[forwardMostMove+1] != NULL) {
7709         free(commentList[forwardMostMove+1]);
7710         commentList[forwardMostMove+1] = NULL;
7711     }
7712     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7713     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7714     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7715                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7716     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7717     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7718     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7719     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7720     gameInfo.result = GameUnfinished;
7721     if (gameInfo.resultDetails != NULL) {
7722         free(gameInfo.resultDetails);
7723         gameInfo.resultDetails = NULL;
7724     }
7725     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7726                               moveList[forwardMostMove - 1]);
7727     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7728                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7729                              fromY, fromX, toY, toX, promoChar,
7730                              parseList[forwardMostMove - 1]);
7731     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7732                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7733                             castlingRights[forwardMostMove]) ) {
7734       case MT_NONE:
7735       case MT_STALEMATE:
7736       default:
7737         break;
7738       case MT_CHECK:
7739         if(gameInfo.variant != VariantShogi)
7740             strcat(parseList[forwardMostMove - 1], "+");
7741         break;
7742       case MT_CHECKMATE:
7743       case MT_STAINMATE:
7744         strcat(parseList[forwardMostMove - 1], "#");
7745         break;
7746     }
7747     if (appData.debugMode) {
7748         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7749     }
7750
7751 }
7752
7753 /* Updates currentMove if not pausing */
7754 void
7755 ShowMove(fromX, fromY, toX, toY)
7756 {
7757     int instant = (gameMode == PlayFromGameFile) ?
7758         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7759     if(appData.noGUI) return;
7760     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7761         if (!instant) {
7762             if (forwardMostMove == currentMove + 1) {
7763                 AnimateMove(boards[forwardMostMove - 1],
7764                             fromX, fromY, toX, toY);
7765             }
7766             if (appData.highlightLastMove) {
7767                 SetHighlights(fromX, fromY, toX, toY);
7768             }
7769         }
7770         currentMove = forwardMostMove;
7771     }
7772
7773     if (instant) return;
7774
7775     DisplayMove(currentMove - 1);
7776     DrawPosition(FALSE, boards[currentMove]);
7777     DisplayBothClocks();
7778     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7779 }
7780
7781 void SendEgtPath(ChessProgramState *cps)
7782 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7783         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7784
7785         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7786
7787         while(*p) {
7788             char c, *q = name+1, *r, *s;
7789
7790             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7791             while(*p && *p != ',') *q++ = *p++;
7792             *q++ = ':'; *q = 0;
7793             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7794                 strcmp(name, ",nalimov:") == 0 ) {
7795                 // take nalimov path from the menu-changeable option first, if it is defined
7796                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7797                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7798             } else
7799             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7800                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7801                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7802                 s = r = StrStr(s, ":") + 1; // beginning of path info
7803                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7804                 c = *r; *r = 0;             // temporarily null-terminate path info
7805                     *--q = 0;               // strip of trailig ':' from name
7806                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7807                 *r = c;
7808                 SendToProgram(buf,cps);     // send egtbpath command for this format
7809             }
7810             if(*p == ',') p++; // read away comma to position for next format name
7811         }
7812 }
7813
7814 void
7815 InitChessProgram(cps, setup)
7816      ChessProgramState *cps;
7817      int setup; /* [HGM] needed to setup FRC opening position */
7818 {
7819     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7820     if (appData.noChessProgram) return;
7821     hintRequested = FALSE;
7822     bookRequested = FALSE;
7823
7824     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7825     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7826     if(cps->memSize) { /* [HGM] memory */
7827         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7828         SendToProgram(buf, cps);
7829     }
7830     SendEgtPath(cps); /* [HGM] EGT */
7831     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7832         sprintf(buf, "cores %d\n", appData.smpCores);
7833         SendToProgram(buf, cps);
7834     }
7835
7836     SendToProgram(cps->initString, cps);
7837     if (gameInfo.variant != VariantNormal &&
7838         gameInfo.variant != VariantLoadable
7839         /* [HGM] also send variant if board size non-standard */
7840         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7841                                             ) {
7842       char *v = VariantName(gameInfo.variant);
7843       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7844         /* [HGM] in protocol 1 we have to assume all variants valid */
7845         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7846         DisplayFatalError(buf, 0, 1);
7847         return;
7848       }
7849
7850       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7851       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7852       if( gameInfo.variant == VariantXiangqi )
7853            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7854       if( gameInfo.variant == VariantShogi )
7855            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7856       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7857            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7858       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7859                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7860            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861       if( gameInfo.variant == VariantCourier )
7862            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863       if( gameInfo.variant == VariantSuper )
7864            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865       if( gameInfo.variant == VariantGreat )
7866            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7867
7868       if(overruled) {
7869            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7870                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7871            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7872            if(StrStr(cps->variants, b) == NULL) { 
7873                // specific sized variant not known, check if general sizing allowed
7874                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7875                    if(StrStr(cps->variants, "boardsize") == NULL) {
7876                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7877                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7878                        DisplayFatalError(buf, 0, 1);
7879                        return;
7880                    }
7881                    /* [HGM] here we really should compare with the maximum supported board size */
7882                }
7883            }
7884       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7885       sprintf(buf, "variant %s\n", b);
7886       SendToProgram(buf, cps);
7887     }
7888     currentlyInitializedVariant = gameInfo.variant;
7889
7890     /* [HGM] send opening position in FRC to first engine */
7891     if(setup) {
7892           SendToProgram("force\n", cps);
7893           SendBoard(cps, 0);
7894           /* engine is now in force mode! Set flag to wake it up after first move. */
7895           setboardSpoiledMachineBlack = 1;
7896     }
7897
7898     if (cps->sendICS) {
7899       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7900       SendToProgram(buf, cps);
7901     }
7902     cps->maybeThinking = FALSE;
7903     cps->offeredDraw = 0;
7904     if (!appData.icsActive) {
7905         SendTimeControl(cps, movesPerSession, timeControl,
7906                         timeIncrement, appData.searchDepth,
7907                         searchTime);
7908     }
7909     if (appData.showThinking 
7910         // [HGM] thinking: four options require thinking output to be sent
7911         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7912                                 ) {
7913         SendToProgram("post\n", cps);
7914     }
7915     SendToProgram("hard\n", cps);
7916     if (!appData.ponderNextMove) {
7917         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7918            it without being sure what state we are in first.  "hard"
7919            is not a toggle, so that one is OK.
7920          */
7921         SendToProgram("easy\n", cps);
7922     }
7923     if (cps->usePing) {
7924       sprintf(buf, "ping %d\n", ++cps->lastPing);
7925       SendToProgram(buf, cps);
7926     }
7927     cps->initDone = TRUE;
7928 }   
7929
7930
7931 void
7932 StartChessProgram(cps)
7933      ChessProgramState *cps;
7934 {
7935     char buf[MSG_SIZ];
7936     int err;
7937
7938     if (appData.noChessProgram) return;
7939     cps->initDone = FALSE;
7940
7941     if (strcmp(cps->host, "localhost") == 0) {
7942         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7943     } else if (*appData.remoteShell == NULLCHAR) {
7944         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7945     } else {
7946         if (*appData.remoteUser == NULLCHAR) {
7947           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7948                     cps->program);
7949         } else {
7950           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7951                     cps->host, appData.remoteUser, cps->program);
7952         }
7953         err = StartChildProcess(buf, "", &cps->pr);
7954     }
7955     
7956     if (err != 0) {
7957         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7958         DisplayFatalError(buf, err, 1);
7959         cps->pr = NoProc;
7960         cps->isr = NULL;
7961         return;
7962     }
7963     
7964     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7965     if (cps->protocolVersion > 1) {
7966       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7967       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7968       cps->comboCnt = 0;  //                and values of combo boxes
7969       SendToProgram(buf, cps);
7970     } else {
7971       SendToProgram("xboard\n", cps);
7972     }
7973 }
7974
7975
7976 void
7977 TwoMachinesEventIfReady P((void))
7978 {
7979   if (first.lastPing != first.lastPong) {
7980     DisplayMessage("", _("Waiting for first chess program"));
7981     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7982     return;
7983   }
7984   if (second.lastPing != second.lastPong) {
7985     DisplayMessage("", _("Waiting for second chess program"));
7986     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7987     return;
7988   }
7989   ThawUI();
7990   TwoMachinesEvent();
7991 }
7992
7993 void
7994 NextMatchGame P((void))
7995 {
7996     int index; /* [HGM] autoinc: step load index during match */
7997     Reset(FALSE, TRUE);
7998     if (*appData.loadGameFile != NULLCHAR) {
7999         index = appData.loadGameIndex;
8000         if(index < 0) { // [HGM] autoinc
8001             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8002             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8003         } 
8004         LoadGameFromFile(appData.loadGameFile,
8005                          index,
8006                          appData.loadGameFile, FALSE);
8007     } else if (*appData.loadPositionFile != NULLCHAR) {
8008         index = appData.loadPositionIndex;
8009         if(index < 0) { // [HGM] autoinc
8010             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8011             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8012         } 
8013         LoadPositionFromFile(appData.loadPositionFile,
8014                              index,
8015                              appData.loadPositionFile);
8016     }
8017     TwoMachinesEventIfReady();
8018 }
8019
8020 void UserAdjudicationEvent( int result )
8021 {
8022     ChessMove gameResult = GameIsDrawn;
8023
8024     if( result > 0 ) {
8025         gameResult = WhiteWins;
8026     }
8027     else if( result < 0 ) {
8028         gameResult = BlackWins;
8029     }
8030
8031     if( gameMode == TwoMachinesPlay ) {
8032         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8033     }
8034 }
8035
8036
8037 // [HGM] save: calculate checksum of game to make games easily identifiable
8038 int StringCheckSum(char *s)
8039 {
8040         int i = 0;
8041         if(s==NULL) return 0;
8042         while(*s) i = i*259 + *s++;
8043         return i;
8044 }
8045
8046 int GameCheckSum()
8047 {
8048         int i, sum=0;
8049         for(i=backwardMostMove; i<forwardMostMove; i++) {
8050                 sum += pvInfoList[i].depth;
8051                 sum += StringCheckSum(parseList[i]);
8052                 sum += StringCheckSum(commentList[i]);
8053                 sum *= 261;
8054         }
8055         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8056         return sum + StringCheckSum(commentList[i]);
8057 } // end of save patch
8058
8059 void
8060 GameEnds(result, resultDetails, whosays)
8061      ChessMove result;
8062      char *resultDetails;
8063      int whosays;
8064 {
8065     GameMode nextGameMode;
8066     int isIcsGame;
8067     char buf[MSG_SIZ];
8068
8069     if(endingGame) return; /* [HGM] crash: forbid recursion */
8070     endingGame = 1;
8071
8072     if (appData.debugMode) {
8073       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8074               result, resultDetails ? resultDetails : "(null)", whosays);
8075     }
8076
8077     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8078         /* If we are playing on ICS, the server decides when the
8079            game is over, but the engine can offer to draw, claim 
8080            a draw, or resign. 
8081          */
8082 #if ZIPPY
8083         if (appData.zippyPlay && first.initDone) {
8084             if (result == GameIsDrawn) {
8085                 /* In case draw still needs to be claimed */
8086                 SendToICS(ics_prefix);
8087                 SendToICS("draw\n");
8088             } else if (StrCaseStr(resultDetails, "resign")) {
8089                 SendToICS(ics_prefix);
8090                 SendToICS("resign\n");
8091             }
8092         }
8093 #endif
8094         endingGame = 0; /* [HGM] crash */
8095         return;
8096     }
8097
8098     /* If we're loading the game from a file, stop */
8099     if (whosays == GE_FILE) {
8100       (void) StopLoadGameTimer();
8101       gameFileFP = NULL;
8102     }
8103
8104     /* Cancel draw offers */
8105     first.offeredDraw = second.offeredDraw = 0;
8106
8107     /* If this is an ICS game, only ICS can really say it's done;
8108        if not, anyone can. */
8109     isIcsGame = (gameMode == IcsPlayingWhite || 
8110                  gameMode == IcsPlayingBlack || 
8111                  gameMode == IcsObserving    || 
8112                  gameMode == IcsExamining);
8113
8114     if (!isIcsGame || whosays == GE_ICS) {
8115         /* OK -- not an ICS game, or ICS said it was done */
8116         StopClocks();
8117         if (!isIcsGame && !appData.noChessProgram) 
8118           SetUserThinkingEnables();
8119     
8120         /* [HGM] if a machine claims the game end we verify this claim */
8121         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8122             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8123                 char claimer;
8124                 ChessMove trueResult = (ChessMove) -1;
8125
8126                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8127                                             first.twoMachinesColor[0] :
8128                                             second.twoMachinesColor[0] ;
8129
8130                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8131                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8132                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8133                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8134                 } else
8135                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8136                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8137                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8138                 } else
8139                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8140                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8141                 }
8142
8143                 // now verify win claims, but not in drop games, as we don't understand those yet
8144                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8145                                                  || gameInfo.variant == VariantGreat) &&
8146                     (result == WhiteWins && claimer == 'w' ||
8147                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8148                       if (appData.debugMode) {
8149                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8150                                 result, epStatus[forwardMostMove], forwardMostMove);
8151                       }
8152                       if(result != trueResult) {
8153                               sprintf(buf, "False win claim: '%s'", resultDetails);
8154                               result = claimer == 'w' ? BlackWins : WhiteWins;
8155                               resultDetails = buf;
8156                       }
8157                 } else
8158                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8159                     && (forwardMostMove <= backwardMostMove ||
8160                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8161                         (claimer=='b')==(forwardMostMove&1))
8162                                                                                   ) {
8163                       /* [HGM] verify: draws that were not flagged are false claims */
8164                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8165                       result = claimer == 'w' ? BlackWins : WhiteWins;
8166                       resultDetails = buf;
8167                 }
8168                 /* (Claiming a loss is accepted no questions asked!) */
8169             }
8170             /* [HGM] bare: don't allow bare King to win */
8171             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8172                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8173                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8174                && result != GameIsDrawn)
8175             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8176                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8177                         int p = (int)boards[forwardMostMove][i][j] - color;
8178                         if(p >= 0 && p <= (int)WhiteKing) k++;
8179                 }
8180                 if (appData.debugMode) {
8181                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8182                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8183                 }
8184                 if(k <= 1) {
8185                         result = GameIsDrawn;
8186                         sprintf(buf, "%s but bare king", resultDetails);
8187                         resultDetails = buf;
8188                 }
8189             }
8190         }
8191
8192
8193         if(serverMoves != NULL && !loadFlag) { char c = '=';
8194             if(result==WhiteWins) c = '+';
8195             if(result==BlackWins) c = '-';
8196             if(resultDetails != NULL)
8197                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8198         }
8199         if (resultDetails != NULL) {
8200             gameInfo.result = result;
8201             gameInfo.resultDetails = StrSave(resultDetails);
8202
8203             /* display last move only if game was not loaded from file */
8204             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8205                 DisplayMove(currentMove - 1);
8206     
8207             if (forwardMostMove != 0) {
8208                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8209                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8210                                                                 ) {
8211                     if (*appData.saveGameFile != NULLCHAR) {
8212                         SaveGameToFile(appData.saveGameFile, TRUE);
8213                     } else if (appData.autoSaveGames) {
8214                         AutoSaveGame();
8215                     }
8216                     if (*appData.savePositionFile != NULLCHAR) {
8217                         SavePositionToFile(appData.savePositionFile);
8218                     }
8219                 }
8220             }
8221
8222             /* Tell program how game ended in case it is learning */
8223             /* [HGM] Moved this to after saving the PGN, just in case */
8224             /* engine died and we got here through time loss. In that */
8225             /* case we will get a fatal error writing the pipe, which */
8226             /* would otherwise lose us the PGN.                       */
8227             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8228             /* output during GameEnds should never be fatal anymore   */
8229             if (gameMode == MachinePlaysWhite ||
8230                 gameMode == MachinePlaysBlack ||
8231                 gameMode == TwoMachinesPlay ||
8232                 gameMode == IcsPlayingWhite ||
8233                 gameMode == IcsPlayingBlack ||
8234                 gameMode == BeginningOfGame) {
8235                 char buf[MSG_SIZ];
8236                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8237                         resultDetails);
8238                 if (first.pr != NoProc) {
8239                     SendToProgram(buf, &first);
8240                 }
8241                 if (second.pr != NoProc &&
8242                     gameMode == TwoMachinesPlay) {
8243                     SendToProgram(buf, &second);
8244                 }
8245             }
8246         }
8247
8248         if (appData.icsActive) {
8249             if (appData.quietPlay &&
8250                 (gameMode == IcsPlayingWhite ||
8251                  gameMode == IcsPlayingBlack)) {
8252                 SendToICS(ics_prefix);
8253                 SendToICS("set shout 1\n");
8254             }
8255             nextGameMode = IcsIdle;
8256             ics_user_moved = FALSE;
8257             /* clean up premove.  It's ugly when the game has ended and the
8258              * premove highlights are still on the board.
8259              */
8260             if (gotPremove) {
8261               gotPremove = FALSE;
8262               ClearPremoveHighlights();
8263               DrawPosition(FALSE, boards[currentMove]);
8264             }
8265             if (whosays == GE_ICS) {
8266                 switch (result) {
8267                 case WhiteWins:
8268                     if (gameMode == IcsPlayingWhite)
8269                         PlayIcsWinSound();
8270                     else if(gameMode == IcsPlayingBlack)
8271                         PlayIcsLossSound();
8272                     break;
8273                 case BlackWins:
8274                     if (gameMode == IcsPlayingBlack)
8275                         PlayIcsWinSound();
8276                     else if(gameMode == IcsPlayingWhite)
8277                         PlayIcsLossSound();
8278                     break;
8279                 case GameIsDrawn:
8280                     PlayIcsDrawSound();
8281                     break;
8282                 default:
8283                     PlayIcsUnfinishedSound();
8284                 }
8285             }
8286         } else if (gameMode == EditGame ||
8287                    gameMode == PlayFromGameFile || 
8288                    gameMode == AnalyzeMode || 
8289                    gameMode == AnalyzeFile) {
8290             nextGameMode = gameMode;
8291         } else {
8292             nextGameMode = EndOfGame;
8293         }
8294         pausing = FALSE;
8295         ModeHighlight();
8296     } else {
8297         nextGameMode = gameMode;
8298     }
8299
8300     if (appData.noChessProgram) {
8301         gameMode = nextGameMode;
8302         ModeHighlight();
8303         endingGame = 0; /* [HGM] crash */
8304         return;
8305     }
8306
8307     if (first.reuse) {
8308         /* Put first chess program into idle state */
8309         if (first.pr != NoProc &&
8310             (gameMode == MachinePlaysWhite ||
8311              gameMode == MachinePlaysBlack ||
8312              gameMode == TwoMachinesPlay ||
8313              gameMode == IcsPlayingWhite ||
8314              gameMode == IcsPlayingBlack ||
8315              gameMode == BeginningOfGame)) {
8316             SendToProgram("force\n", &first);
8317             if (first.usePing) {
8318               char buf[MSG_SIZ];
8319               sprintf(buf, "ping %d\n", ++first.lastPing);
8320               SendToProgram(buf, &first);
8321             }
8322         }
8323     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8324         /* Kill off first chess program */
8325         if (first.isr != NULL)
8326           RemoveInputSource(first.isr);
8327         first.isr = NULL;
8328     
8329         if (first.pr != NoProc) {
8330             ExitAnalyzeMode();
8331             DoSleep( appData.delayBeforeQuit );
8332             SendToProgram("quit\n", &first);
8333             DoSleep( appData.delayAfterQuit );
8334             DestroyChildProcess(first.pr, first.useSigterm);
8335         }
8336         first.pr = NoProc;
8337     }
8338     if (second.reuse) {
8339         /* Put second chess program into idle state */
8340         if (second.pr != NoProc &&
8341             gameMode == TwoMachinesPlay) {
8342             SendToProgram("force\n", &second);
8343             if (second.usePing) {
8344               char buf[MSG_SIZ];
8345               sprintf(buf, "ping %d\n", ++second.lastPing);
8346               SendToProgram(buf, &second);
8347             }
8348         }
8349     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8350         /* Kill off second chess program */
8351         if (second.isr != NULL)
8352           RemoveInputSource(second.isr);
8353         second.isr = NULL;
8354     
8355         if (second.pr != NoProc) {
8356             DoSleep( appData.delayBeforeQuit );
8357             SendToProgram("quit\n", &second);
8358             DoSleep( appData.delayAfterQuit );
8359             DestroyChildProcess(second.pr, second.useSigterm);
8360         }
8361         second.pr = NoProc;
8362     }
8363
8364     if (matchMode && gameMode == TwoMachinesPlay) {
8365         switch (result) {
8366         case WhiteWins:
8367           if (first.twoMachinesColor[0] == 'w') {
8368             first.matchWins++;
8369           } else {
8370             second.matchWins++;
8371           }
8372           break;
8373         case BlackWins:
8374           if (first.twoMachinesColor[0] == 'b') {
8375             first.matchWins++;
8376           } else {
8377             second.matchWins++;
8378           }
8379           break;
8380         default:
8381           break;
8382         }
8383         if (matchGame < appData.matchGames) {
8384             char *tmp;
8385             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8386                 tmp = first.twoMachinesColor;
8387                 first.twoMachinesColor = second.twoMachinesColor;
8388                 second.twoMachinesColor = tmp;
8389             }
8390             gameMode = nextGameMode;
8391             matchGame++;
8392             if(appData.matchPause>10000 || appData.matchPause<10)
8393                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8394             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8395             endingGame = 0; /* [HGM] crash */
8396             return;
8397         } else {
8398             char buf[MSG_SIZ];
8399             gameMode = nextGameMode;
8400             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8401                     first.tidy, second.tidy,
8402                     first.matchWins, second.matchWins,
8403                     appData.matchGames - (first.matchWins + second.matchWins));
8404             DisplayFatalError(buf, 0, 0);
8405         }
8406     }
8407     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8408         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8409       ExitAnalyzeMode();
8410     gameMode = nextGameMode;
8411     ModeHighlight();
8412     endingGame = 0;  /* [HGM] crash */
8413 }
8414
8415 /* Assumes program was just initialized (initString sent).
8416    Leaves program in force mode. */
8417 void
8418 FeedMovesToProgram(cps, upto) 
8419      ChessProgramState *cps;
8420      int upto;
8421 {
8422     int i;
8423     
8424     if (appData.debugMode)
8425       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8426               startedFromSetupPosition ? "position and " : "",
8427               backwardMostMove, upto, cps->which);
8428     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8429         // [HGM] variantswitch: make engine aware of new variant
8430         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8431                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8432         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8433         SendToProgram(buf, cps);
8434         currentlyInitializedVariant = gameInfo.variant;
8435     }
8436     SendToProgram("force\n", cps);
8437     if (startedFromSetupPosition) {
8438         SendBoard(cps, backwardMostMove);
8439     if (appData.debugMode) {
8440         fprintf(debugFP, "feedMoves\n");
8441     }
8442     }
8443     for (i = backwardMostMove; i < upto; i++) {
8444         SendMoveToProgram(i, cps);
8445     }
8446 }
8447
8448
8449 void
8450 ResurrectChessProgram()
8451 {
8452      /* The chess program may have exited.
8453         If so, restart it and feed it all the moves made so far. */
8454
8455     if (appData.noChessProgram || first.pr != NoProc) return;
8456     
8457     StartChessProgram(&first);
8458     InitChessProgram(&first, FALSE);
8459     FeedMovesToProgram(&first, currentMove);
8460
8461     if (!first.sendTime) {
8462         /* can't tell gnuchess what its clock should read,
8463            so we bow to its notion. */
8464         ResetClocks();
8465         timeRemaining[0][currentMove] = whiteTimeRemaining;
8466         timeRemaining[1][currentMove] = blackTimeRemaining;
8467     }
8468
8469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8470                 appData.icsEngineAnalyze) && first.analysisSupport) {
8471       SendToProgram("analyze\n", &first);
8472       first.analyzing = TRUE;
8473     }
8474 }
8475
8476 /*
8477  * Button procedures
8478  */
8479 void
8480 Reset(redraw, init)
8481      int redraw, init;
8482 {
8483     int i;
8484
8485     if (appData.debugMode) {
8486         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8487                 redraw, init, gameMode);
8488     }
8489     pausing = pauseExamInvalid = FALSE;
8490     startedFromSetupPosition = blackPlaysFirst = FALSE;
8491     firstMove = TRUE;
8492     whiteFlag = blackFlag = FALSE;
8493     userOfferedDraw = FALSE;
8494     hintRequested = bookRequested = FALSE;
8495     first.maybeThinking = FALSE;
8496     second.maybeThinking = FALSE;
8497     first.bookSuspend = FALSE; // [HGM] book
8498     second.bookSuspend = FALSE;
8499     thinkOutput[0] = NULLCHAR;
8500     lastHint[0] = NULLCHAR;
8501     ClearGameInfo(&gameInfo);
8502     gameInfo.variant = StringToVariant(appData.variant);
8503     ics_user_moved = ics_clock_paused = FALSE;
8504     ics_getting_history = H_FALSE;
8505     ics_gamenum = -1;
8506     white_holding[0] = black_holding[0] = NULLCHAR;
8507     ClearProgramStats();
8508     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8509     
8510     ResetFrontEnd();
8511     ClearHighlights();
8512     flipView = appData.flipView;
8513     ClearPremoveHighlights();
8514     gotPremove = FALSE;
8515     alarmSounded = FALSE;
8516
8517     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8518     if(appData.serverMovesName != NULL) {
8519         /* [HGM] prepare to make moves file for broadcasting */
8520         clock_t t = clock();
8521         if(serverMoves != NULL) fclose(serverMoves);
8522         serverMoves = fopen(appData.serverMovesName, "r");
8523         if(serverMoves != NULL) {
8524             fclose(serverMoves);
8525             /* delay 15 sec before overwriting, so all clients can see end */
8526             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8527         }
8528         serverMoves = fopen(appData.serverMovesName, "w");
8529     }
8530
8531     ExitAnalyzeMode();
8532     gameMode = BeginningOfGame;
8533     ModeHighlight();
8534     if(appData.icsActive) gameInfo.variant = VariantNormal;
8535     currentMove = forwardMostMove = backwardMostMove = 0;
8536     InitPosition(redraw);
8537     for (i = 0; i < MAX_MOVES; i++) {
8538         if (commentList[i] != NULL) {
8539             free(commentList[i]);
8540             commentList[i] = NULL;
8541         }
8542     }
8543     ResetClocks();
8544     timeRemaining[0][0] = whiteTimeRemaining;
8545     timeRemaining[1][0] = blackTimeRemaining;
8546     if (first.pr == NULL) {
8547         StartChessProgram(&first);
8548     }
8549     if (init) {
8550             InitChessProgram(&first, startedFromSetupPosition);
8551     }
8552     DisplayTitle("");
8553     DisplayMessage("", "");
8554     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8555     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8556 }
8557
8558 void
8559 AutoPlayGameLoop()
8560 {
8561     for (;;) {
8562         if (!AutoPlayOneMove())
8563           return;
8564         if (matchMode || appData.timeDelay == 0)
8565           continue;
8566         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8567           return;
8568         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8569         break;
8570     }
8571 }
8572
8573
8574 int
8575 AutoPlayOneMove()
8576 {
8577     int fromX, fromY, toX, toY;
8578
8579     if (appData.debugMode) {
8580       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8581     }
8582
8583     if (gameMode != PlayFromGameFile)
8584       return FALSE;
8585
8586     if (currentMove >= forwardMostMove) {
8587       gameMode = EditGame;
8588       ModeHighlight();
8589
8590       /* [AS] Clear current move marker at the end of a game */
8591       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8592
8593       return FALSE;
8594     }
8595     
8596     toX = moveList[currentMove][2] - AAA;
8597     toY = moveList[currentMove][3] - ONE;
8598
8599     if (moveList[currentMove][1] == '@') {
8600         if (appData.highlightLastMove) {
8601             SetHighlights(-1, -1, toX, toY);
8602         }
8603     } else {
8604         fromX = moveList[currentMove][0] - AAA;
8605         fromY = moveList[currentMove][1] - ONE;
8606
8607         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8608
8609         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8610
8611         if (appData.highlightLastMove) {
8612             SetHighlights(fromX, fromY, toX, toY);
8613         }
8614     }
8615     DisplayMove(currentMove);
8616     SendMoveToProgram(currentMove++, &first);
8617     DisplayBothClocks();
8618     DrawPosition(FALSE, boards[currentMove]);
8619     // [HGM] PV info: always display, routine tests if empty
8620     DisplayComment(currentMove - 1, commentList[currentMove]);
8621     return TRUE;
8622 }
8623
8624
8625 int
8626 LoadGameOneMove(readAhead)
8627      ChessMove readAhead;
8628 {
8629     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8630     char promoChar = NULLCHAR;
8631     ChessMove moveType;
8632     char move[MSG_SIZ];
8633     char *p, *q;
8634     
8635     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8636         gameMode != AnalyzeMode && gameMode != Training) {
8637         gameFileFP = NULL;
8638         return FALSE;
8639     }
8640     
8641     yyboardindex = forwardMostMove;
8642     if (readAhead != (ChessMove)0) {
8643       moveType = readAhead;
8644     } else {
8645       if (gameFileFP == NULL)
8646           return FALSE;
8647       moveType = (ChessMove) yylex();
8648     }
8649     
8650     done = FALSE;
8651     switch (moveType) {
8652       case Comment:
8653         if (appData.debugMode) 
8654           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8655         p = yy_text;
8656         if (*p == '{' || *p == '[' || *p == '(') {
8657             p[strlen(p) - 1] = NULLCHAR;
8658             p++;
8659         }
8660
8661         /* append the comment but don't display it */
8662         while (*p == '\n') p++;
8663         AppendComment(currentMove, p);
8664         return TRUE;
8665
8666       case WhiteCapturesEnPassant:
8667       case BlackCapturesEnPassant:
8668       case WhitePromotionChancellor:
8669       case BlackPromotionChancellor:
8670       case WhitePromotionArchbishop:
8671       case BlackPromotionArchbishop:
8672       case WhitePromotionCentaur:
8673       case BlackPromotionCentaur:
8674       case WhitePromotionQueen:
8675       case BlackPromotionQueen:
8676       case WhitePromotionRook:
8677       case BlackPromotionRook:
8678       case WhitePromotionBishop:
8679       case BlackPromotionBishop:
8680       case WhitePromotionKnight:
8681       case BlackPromotionKnight:
8682       case WhitePromotionKing:
8683       case BlackPromotionKing:
8684       case NormalMove:
8685       case WhiteKingSideCastle:
8686       case WhiteQueenSideCastle:
8687       case BlackKingSideCastle:
8688       case BlackQueenSideCastle:
8689       case WhiteKingSideCastleWild:
8690       case WhiteQueenSideCastleWild:
8691       case BlackKingSideCastleWild:
8692       case BlackQueenSideCastleWild:
8693       /* PUSH Fabien */
8694       case WhiteHSideCastleFR:
8695       case WhiteASideCastleFR:
8696       case BlackHSideCastleFR:
8697       case BlackASideCastleFR:
8698       /* POP Fabien */
8699         if (appData.debugMode)
8700           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8701         fromX = currentMoveString[0] - AAA;
8702         fromY = currentMoveString[1] - ONE;
8703         toX = currentMoveString[2] - AAA;
8704         toY = currentMoveString[3] - ONE;
8705         promoChar = currentMoveString[4];
8706         break;
8707
8708       case WhiteDrop:
8709       case BlackDrop:
8710         if (appData.debugMode)
8711           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8712         fromX = moveType == WhiteDrop ?
8713           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8714         (int) CharToPiece(ToLower(currentMoveString[0]));
8715         fromY = DROP_RANK;
8716         toX = currentMoveString[2] - AAA;
8717         toY = currentMoveString[3] - ONE;
8718         break;
8719
8720       case WhiteWins:
8721       case BlackWins:
8722       case GameIsDrawn:
8723       case GameUnfinished:
8724         if (appData.debugMode)
8725           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8726         p = strchr(yy_text, '{');
8727         if (p == NULL) p = strchr(yy_text, '(');
8728         if (p == NULL) {
8729             p = yy_text;
8730             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8731         } else {
8732             q = strchr(p, *p == '{' ? '}' : ')');
8733             if (q != NULL) *q = NULLCHAR;
8734             p++;
8735         }
8736         GameEnds(moveType, p, GE_FILE);
8737         done = TRUE;
8738         if (cmailMsgLoaded) {
8739             ClearHighlights();
8740             flipView = WhiteOnMove(currentMove);
8741             if (moveType == GameUnfinished) flipView = !flipView;
8742             if (appData.debugMode)
8743               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8744         }
8745         break;
8746
8747       case (ChessMove) 0:       /* end of file */
8748         if (appData.debugMode)
8749           fprintf(debugFP, "Parser hit end of file\n");
8750         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8751                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8752           case MT_NONE:
8753           case MT_CHECK:
8754             break;
8755           case MT_CHECKMATE:
8756           case MT_STAINMATE:
8757             if (WhiteOnMove(currentMove)) {
8758                 GameEnds(BlackWins, "Black mates", GE_FILE);
8759             } else {
8760                 GameEnds(WhiteWins, "White mates", GE_FILE);
8761             }
8762             break;
8763           case MT_STALEMATE:
8764             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8765             break;
8766         }
8767         done = TRUE;
8768         break;
8769
8770       case MoveNumberOne:
8771         if (lastLoadGameStart == GNUChessGame) {
8772             /* GNUChessGames have numbers, but they aren't move numbers */
8773             if (appData.debugMode)
8774               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8775                       yy_text, (int) moveType);
8776             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8777         }
8778         /* else fall thru */
8779
8780       case XBoardGame:
8781       case GNUChessGame:
8782       case PGNTag:
8783         /* Reached start of next game in file */
8784         if (appData.debugMode)
8785           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8786         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8787                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8788           case MT_NONE:
8789           case MT_CHECK:
8790             break;
8791           case MT_CHECKMATE:
8792           case MT_STAINMATE:
8793             if (WhiteOnMove(currentMove)) {
8794                 GameEnds(BlackWins, "Black mates", GE_FILE);
8795             } else {
8796                 GameEnds(WhiteWins, "White mates", GE_FILE);
8797             }
8798             break;
8799           case MT_STALEMATE:
8800             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8801             break;
8802         }
8803         done = TRUE;
8804         break;
8805
8806       case PositionDiagram:     /* should not happen; ignore */
8807       case ElapsedTime:         /* ignore */
8808       case NAG:                 /* ignore */
8809         if (appData.debugMode)
8810           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8811                   yy_text, (int) moveType);
8812         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8813
8814       case IllegalMove:
8815         if (appData.testLegality) {
8816             if (appData.debugMode)
8817               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8818             sprintf(move, _("Illegal move: %d.%s%s"),
8819                     (forwardMostMove / 2) + 1,
8820                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8821             DisplayError(move, 0);
8822             done = TRUE;
8823         } else {
8824             if (appData.debugMode)
8825               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8826                       yy_text, currentMoveString);
8827             fromX = currentMoveString[0] - AAA;
8828             fromY = currentMoveString[1] - ONE;
8829             toX = currentMoveString[2] - AAA;
8830             toY = currentMoveString[3] - ONE;
8831             promoChar = currentMoveString[4];
8832         }
8833         break;
8834
8835       case AmbiguousMove:
8836         if (appData.debugMode)
8837           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8838         sprintf(move, _("Ambiguous move: %d.%s%s"),
8839                 (forwardMostMove / 2) + 1,
8840                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8841         DisplayError(move, 0);
8842         done = TRUE;
8843         break;
8844
8845       default:
8846       case ImpossibleMove:
8847         if (appData.debugMode)
8848           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8849         sprintf(move, _("Illegal move: %d.%s%s"),
8850                 (forwardMostMove / 2) + 1,
8851                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8852         DisplayError(move, 0);
8853         done = TRUE;
8854         break;
8855     }
8856
8857     if (done) {
8858         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8859             DrawPosition(FALSE, boards[currentMove]);
8860             DisplayBothClocks();
8861             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8862               DisplayComment(currentMove - 1, commentList[currentMove]);
8863         }
8864         (void) StopLoadGameTimer();
8865         gameFileFP = NULL;
8866         cmailOldMove = forwardMostMove;
8867         return FALSE;
8868     } else {
8869         /* currentMoveString is set as a side-effect of yylex */
8870         strcat(currentMoveString, "\n");
8871         strcpy(moveList[forwardMostMove], currentMoveString);
8872         
8873         thinkOutput[0] = NULLCHAR;
8874         MakeMove(fromX, fromY, toX, toY, promoChar);
8875         currentMove = forwardMostMove;
8876         return TRUE;
8877     }
8878 }
8879
8880 /* Load the nth game from the given file */
8881 int
8882 LoadGameFromFile(filename, n, title, useList)
8883      char *filename;
8884      int n;
8885      char *title;
8886      /*Boolean*/ int useList;
8887 {
8888     FILE *f;
8889     char buf[MSG_SIZ];
8890
8891     if (strcmp(filename, "-") == 0) {
8892         f = stdin;
8893         title = "stdin";
8894     } else {
8895         f = fopen(filename, "rb");
8896         if (f == NULL) {
8897           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8898             DisplayError(buf, errno);
8899             return FALSE;
8900         }
8901     }
8902     if (fseek(f, 0, 0) == -1) {
8903         /* f is not seekable; probably a pipe */
8904         useList = FALSE;
8905     }
8906     if (useList && n == 0) {
8907         int error = GameListBuild(f);
8908         if (error) {
8909             DisplayError(_("Cannot build game list"), error);
8910         } else if (!ListEmpty(&gameList) &&
8911                    ((ListGame *) gameList.tailPred)->number > 1) {
8912             GameListPopUp(f, title);
8913             return TRUE;
8914         }
8915         GameListDestroy();
8916         n = 1;
8917     }
8918     if (n == 0) n = 1;
8919     return LoadGame(f, n, title, FALSE);
8920 }
8921
8922
8923 void
8924 MakeRegisteredMove()
8925 {
8926     int fromX, fromY, toX, toY;
8927     char promoChar;
8928     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8929         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8930           case CMAIL_MOVE:
8931           case CMAIL_DRAW:
8932             if (appData.debugMode)
8933               fprintf(debugFP, "Restoring %s for game %d\n",
8934                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8935     
8936             thinkOutput[0] = NULLCHAR;
8937             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8938             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8939             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8940             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8941             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8942             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8943             MakeMove(fromX, fromY, toX, toY, promoChar);
8944             ShowMove(fromX, fromY, toX, toY);
8945               
8946             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8947                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8948               case MT_NONE:
8949               case MT_CHECK:
8950                 break;
8951                 
8952               case MT_CHECKMATE:
8953               case MT_STAINMATE:
8954                 if (WhiteOnMove(currentMove)) {
8955                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8956                 } else {
8957                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8958                 }
8959                 break;
8960                 
8961               case MT_STALEMATE:
8962                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8963                 break;
8964             }
8965
8966             break;
8967             
8968           case CMAIL_RESIGN:
8969             if (WhiteOnMove(currentMove)) {
8970                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8971             } else {
8972                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8973             }
8974             break;
8975             
8976           case CMAIL_ACCEPT:
8977             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8978             break;
8979               
8980           default:
8981             break;
8982         }
8983     }
8984
8985     return;
8986 }
8987
8988 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8989 int
8990 CmailLoadGame(f, gameNumber, title, useList)
8991      FILE *f;
8992      int gameNumber;
8993      char *title;
8994      int useList;
8995 {
8996     int retVal;
8997
8998     if (gameNumber > nCmailGames) {
8999         DisplayError(_("No more games in this message"), 0);
9000         return FALSE;
9001     }
9002     if (f == lastLoadGameFP) {
9003         int offset = gameNumber - lastLoadGameNumber;
9004         if (offset == 0) {
9005             cmailMsg[0] = NULLCHAR;
9006             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9007                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9008                 nCmailMovesRegistered--;
9009             }
9010             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9011             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9012                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9013             }
9014         } else {
9015             if (! RegisterMove()) return FALSE;
9016         }
9017     }
9018
9019     retVal = LoadGame(f, gameNumber, title, useList);
9020
9021     /* Make move registered during previous look at this game, if any */
9022     MakeRegisteredMove();
9023
9024     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9025         commentList[currentMove]
9026           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9027         DisplayComment(currentMove - 1, commentList[currentMove]);
9028     }
9029
9030     return retVal;
9031 }
9032
9033 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9034 int
9035 ReloadGame(offset)
9036      int offset;
9037 {
9038     int gameNumber = lastLoadGameNumber + offset;
9039     if (lastLoadGameFP == NULL) {
9040         DisplayError(_("No game has been loaded yet"), 0);
9041         return FALSE;
9042     }
9043     if (gameNumber <= 0) {
9044         DisplayError(_("Can't back up any further"), 0);
9045         return FALSE;
9046     }
9047     if (cmailMsgLoaded) {
9048         return CmailLoadGame(lastLoadGameFP, gameNumber,
9049                              lastLoadGameTitle, lastLoadGameUseList);
9050     } else {
9051         return LoadGame(lastLoadGameFP, gameNumber,
9052                         lastLoadGameTitle, lastLoadGameUseList);
9053     }
9054 }
9055
9056
9057
9058 /* Load the nth game from open file f */
9059 int
9060 LoadGame(f, gameNumber, title, useList)
9061      FILE *f;
9062      int gameNumber;
9063      char *title;
9064      int useList;
9065 {
9066     ChessMove cm;
9067     char buf[MSG_SIZ];
9068     int gn = gameNumber;
9069     ListGame *lg = NULL;
9070     int numPGNTags = 0;
9071     int err;
9072     GameMode oldGameMode;
9073     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9074
9075     if (appData.debugMode) 
9076         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9077
9078     if (gameMode == Training )
9079         SetTrainingModeOff();
9080
9081     oldGameMode = gameMode;
9082     if (gameMode != BeginningOfGame) {
9083       Reset(FALSE, TRUE);
9084     }
9085
9086     gameFileFP = f;
9087     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9088         fclose(lastLoadGameFP);
9089     }
9090
9091     if (useList) {
9092         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9093         
9094         if (lg) {
9095             fseek(f, lg->offset, 0);
9096             GameListHighlight(gameNumber);
9097             gn = 1;
9098         }
9099         else {
9100             DisplayError(_("Game number out of range"), 0);
9101             return FALSE;
9102         }
9103     } else {
9104         GameListDestroy();
9105         if (fseek(f, 0, 0) == -1) {
9106             if (f == lastLoadGameFP ?
9107                 gameNumber == lastLoadGameNumber + 1 :
9108                 gameNumber == 1) {
9109                 gn = 1;
9110             } else {
9111                 DisplayError(_("Can't seek on game file"), 0);
9112                 return FALSE;
9113             }
9114         }
9115     }
9116     lastLoadGameFP = f;
9117     lastLoadGameNumber = gameNumber;
9118     strcpy(lastLoadGameTitle, title);
9119     lastLoadGameUseList = useList;
9120
9121     yynewfile(f);
9122
9123     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9124       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9125                 lg->gameInfo.black);
9126             DisplayTitle(buf);
9127     } else if (*title != NULLCHAR) {
9128         if (gameNumber > 1) {
9129             sprintf(buf, "%s %d", title, gameNumber);
9130             DisplayTitle(buf);
9131         } else {
9132             DisplayTitle(title);
9133         }
9134     }
9135
9136     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9137         gameMode = PlayFromGameFile;
9138         ModeHighlight();
9139     }
9140
9141     currentMove = forwardMostMove = backwardMostMove = 0;
9142     CopyBoard(boards[0], initialPosition);
9143     StopClocks();
9144
9145     /*
9146      * Skip the first gn-1 games in the file.
9147      * Also skip over anything that precedes an identifiable 
9148      * start of game marker, to avoid being confused by 
9149      * garbage at the start of the file.  Currently 
9150      * recognized start of game markers are the move number "1",
9151      * the pattern "gnuchess .* game", the pattern
9152      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9153      * A game that starts with one of the latter two patterns
9154      * will also have a move number 1, possibly
9155      * following a position diagram.
9156      * 5-4-02: Let's try being more lenient and allowing a game to
9157      * start with an unnumbered move.  Does that break anything?
9158      */
9159     cm = lastLoadGameStart = (ChessMove) 0;
9160     while (gn > 0) {
9161         yyboardindex = forwardMostMove;
9162         cm = (ChessMove) yylex();
9163         switch (cm) {
9164           case (ChessMove) 0:
9165             if (cmailMsgLoaded) {
9166                 nCmailGames = CMAIL_MAX_GAMES - gn;
9167             } else {
9168                 Reset(TRUE, TRUE);
9169                 DisplayError(_("Game not found in file"), 0);
9170             }
9171             return FALSE;
9172
9173           case GNUChessGame:
9174           case XBoardGame:
9175             gn--;
9176             lastLoadGameStart = cm;
9177             break;
9178             
9179           case MoveNumberOne:
9180             switch (lastLoadGameStart) {
9181               case GNUChessGame:
9182               case XBoardGame:
9183               case PGNTag:
9184                 break;
9185               case MoveNumberOne:
9186               case (ChessMove) 0:
9187                 gn--;           /* count this game */
9188                 lastLoadGameStart = cm;
9189                 break;
9190               default:
9191                 /* impossible */
9192                 break;
9193             }
9194             break;
9195
9196           case PGNTag:
9197             switch (lastLoadGameStart) {
9198               case GNUChessGame:
9199               case PGNTag:
9200               case MoveNumberOne:
9201               case (ChessMove) 0:
9202                 gn--;           /* count this game */
9203                 lastLoadGameStart = cm;
9204                 break;
9205               case XBoardGame:
9206                 lastLoadGameStart = cm; /* game counted already */
9207                 break;
9208               default:
9209                 /* impossible */
9210                 break;
9211             }
9212             if (gn > 0) {
9213                 do {
9214                     yyboardindex = forwardMostMove;
9215                     cm = (ChessMove) yylex();
9216                 } while (cm == PGNTag || cm == Comment);
9217             }
9218             break;
9219
9220           case WhiteWins:
9221           case BlackWins:
9222           case GameIsDrawn:
9223             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9224                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9225                     != CMAIL_OLD_RESULT) {
9226                     nCmailResults ++ ;
9227                     cmailResult[  CMAIL_MAX_GAMES
9228                                 - gn - 1] = CMAIL_OLD_RESULT;
9229                 }
9230             }
9231             break;
9232
9233           case NormalMove:
9234             /* Only a NormalMove can be at the start of a game
9235              * without a position diagram. */
9236             if (lastLoadGameStart == (ChessMove) 0) {
9237               gn--;
9238               lastLoadGameStart = MoveNumberOne;
9239             }
9240             break;
9241
9242           default:
9243             break;
9244         }
9245     }
9246     
9247     if (appData.debugMode)
9248       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9249
9250     if (cm == XBoardGame) {
9251         /* Skip any header junk before position diagram and/or move 1 */
9252         for (;;) {
9253             yyboardindex = forwardMostMove;
9254             cm = (ChessMove) yylex();
9255
9256             if (cm == (ChessMove) 0 ||
9257                 cm == GNUChessGame || cm == XBoardGame) {
9258                 /* Empty game; pretend end-of-file and handle later */
9259                 cm = (ChessMove) 0;
9260                 break;
9261             }
9262
9263             if (cm == MoveNumberOne || cm == PositionDiagram ||
9264                 cm == PGNTag || cm == Comment)
9265               break;
9266         }
9267     } else if (cm == GNUChessGame) {
9268         if (gameInfo.event != NULL) {
9269             free(gameInfo.event);
9270         }
9271         gameInfo.event = StrSave(yy_text);
9272     }   
9273
9274     startedFromSetupPosition = FALSE;
9275     while (cm == PGNTag) {
9276         if (appData.debugMode) 
9277           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9278         err = ParsePGNTag(yy_text, &gameInfo);
9279         if (!err) numPGNTags++;
9280
9281         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9282         if(gameInfo.variant != oldVariant) {
9283             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9284             InitPosition(TRUE);
9285             oldVariant = gameInfo.variant;
9286             if (appData.debugMode) 
9287               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9288         }
9289
9290
9291         if (gameInfo.fen != NULL) {
9292           Board initial_position;
9293           startedFromSetupPosition = TRUE;
9294           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9295             Reset(TRUE, TRUE);
9296             DisplayError(_("Bad FEN position in file"), 0);
9297             return FALSE;
9298           }
9299           CopyBoard(boards[0], initial_position);
9300           if (blackPlaysFirst) {
9301             currentMove = forwardMostMove = backwardMostMove = 1;
9302             CopyBoard(boards[1], initial_position);
9303             strcpy(moveList[0], "");
9304             strcpy(parseList[0], "");
9305             timeRemaining[0][1] = whiteTimeRemaining;
9306             timeRemaining[1][1] = blackTimeRemaining;
9307             if (commentList[0] != NULL) {
9308               commentList[1] = commentList[0];
9309               commentList[0] = NULL;
9310             }
9311           } else {
9312             currentMove = forwardMostMove = backwardMostMove = 0;
9313           }
9314           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9315           {   int i;
9316               initialRulePlies = FENrulePlies;
9317               epStatus[forwardMostMove] = FENepStatus;
9318               for( i=0; i< nrCastlingRights; i++ )
9319                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9320           }
9321           yyboardindex = forwardMostMove;
9322           free(gameInfo.fen);
9323           gameInfo.fen = NULL;
9324         }
9325
9326         yyboardindex = forwardMostMove;
9327         cm = (ChessMove) yylex();
9328
9329         /* Handle comments interspersed among the tags */
9330         while (cm == Comment) {
9331             char *p;
9332             if (appData.debugMode) 
9333               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9334             p = yy_text;
9335             if (*p == '{' || *p == '[' || *p == '(') {
9336                 p[strlen(p) - 1] = NULLCHAR;
9337                 p++;
9338             }
9339             while (*p == '\n') p++;
9340             AppendComment(currentMove, p);
9341             yyboardindex = forwardMostMove;
9342             cm = (ChessMove) yylex();
9343         }
9344     }
9345
9346     /* don't rely on existence of Event tag since if game was
9347      * pasted from clipboard the Event tag may not exist
9348      */
9349     if (numPGNTags > 0){
9350         char *tags;
9351         if (gameInfo.variant == VariantNormal) {
9352           gameInfo.variant = StringToVariant(gameInfo.event);
9353         }
9354         if (!matchMode) {
9355           if( appData.autoDisplayTags ) {
9356             tags = PGNTags(&gameInfo);
9357             TagsPopUp(tags, CmailMsg());
9358             free(tags);
9359           }
9360         }
9361     } else {
9362         /* Make something up, but don't display it now */
9363         SetGameInfo();
9364         TagsPopDown();
9365     }
9366
9367     if (cm == PositionDiagram) {
9368         int i, j;
9369         char *p;
9370         Board initial_position;
9371
9372         if (appData.debugMode)
9373           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9374
9375         if (!startedFromSetupPosition) {
9376             p = yy_text;
9377             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9378               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9379                 switch (*p) {
9380                   case '[':
9381                   case '-':
9382                   case ' ':
9383                   case '\t':
9384                   case '\n':
9385                   case '\r':
9386                     break;
9387                   default:
9388                     initial_position[i][j++] = CharToPiece(*p);
9389                     break;
9390                 }
9391             while (*p == ' ' || *p == '\t' ||
9392                    *p == '\n' || *p == '\r') p++;
9393         
9394             if (strncmp(p, "black", strlen("black"))==0)
9395               blackPlaysFirst = TRUE;
9396             else
9397               blackPlaysFirst = FALSE;
9398             startedFromSetupPosition = TRUE;
9399         
9400             CopyBoard(boards[0], initial_position);
9401             if (blackPlaysFirst) {
9402                 currentMove = forwardMostMove = backwardMostMove = 1;
9403                 CopyBoard(boards[1], initial_position);
9404                 strcpy(moveList[0], "");
9405                 strcpy(parseList[0], "");
9406                 timeRemaining[0][1] = whiteTimeRemaining;
9407                 timeRemaining[1][1] = blackTimeRemaining;
9408                 if (commentList[0] != NULL) {
9409                     commentList[1] = commentList[0];
9410                     commentList[0] = NULL;
9411                 }
9412             } else {
9413                 currentMove = forwardMostMove = backwardMostMove = 0;
9414             }
9415         }
9416         yyboardindex = forwardMostMove;
9417         cm = (ChessMove) yylex();
9418     }
9419
9420     if (first.pr == NoProc) {
9421         StartChessProgram(&first);
9422     }
9423     InitChessProgram(&first, FALSE);
9424     SendToProgram("force\n", &first);
9425     if (startedFromSetupPosition) {
9426         SendBoard(&first, forwardMostMove);
9427     if (appData.debugMode) {
9428         fprintf(debugFP, "Load Game\n");
9429     }
9430         DisplayBothClocks();
9431     }      
9432
9433     /* [HGM] server: flag to write setup moves in broadcast file as one */
9434     loadFlag = appData.suppressLoadMoves;
9435
9436     while (cm == Comment) {
9437         char *p;
9438         if (appData.debugMode) 
9439           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9440         p = yy_text;
9441         if (*p == '{' || *p == '[' || *p == '(') {
9442             p[strlen(p) - 1] = NULLCHAR;
9443             p++;
9444         }
9445         while (*p == '\n') p++;
9446         AppendComment(currentMove, p);
9447         yyboardindex = forwardMostMove;
9448         cm = (ChessMove) yylex();
9449     }
9450
9451     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9452         cm == WhiteWins || cm == BlackWins ||
9453         cm == GameIsDrawn || cm == GameUnfinished) {
9454         DisplayMessage("", _("No moves in game"));
9455         if (cmailMsgLoaded) {
9456             if (appData.debugMode)
9457               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9458             ClearHighlights();
9459             flipView = FALSE;
9460         }
9461         DrawPosition(FALSE, boards[currentMove]);
9462         DisplayBothClocks();
9463         gameMode = EditGame;
9464         ModeHighlight();
9465         gameFileFP = NULL;
9466         cmailOldMove = 0;
9467         return TRUE;
9468     }
9469
9470     // [HGM] PV info: routine tests if comment empty
9471     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9472         DisplayComment(currentMove - 1, commentList[currentMove]);
9473     }
9474     if (!matchMode && appData.timeDelay != 0) 
9475       DrawPosition(FALSE, boards[currentMove]);
9476
9477     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9478       programStats.ok_to_send = 1;
9479     }
9480
9481     /* if the first token after the PGN tags is a move
9482      * and not move number 1, retrieve it from the parser 
9483      */
9484     if (cm != MoveNumberOne)
9485         LoadGameOneMove(cm);
9486
9487     /* load the remaining moves from the file */
9488     while (LoadGameOneMove((ChessMove)0)) {
9489       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9490       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9491     }
9492
9493     /* rewind to the start of the game */
9494     currentMove = backwardMostMove;
9495
9496     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9497
9498     if (oldGameMode == AnalyzeFile ||
9499         oldGameMode == AnalyzeMode) {
9500       AnalyzeFileEvent();
9501     }
9502
9503     if (matchMode || appData.timeDelay == 0) {
9504       ToEndEvent();
9505       gameMode = EditGame;
9506       ModeHighlight();
9507     } else if (appData.timeDelay > 0) {
9508       AutoPlayGameLoop();
9509     }
9510
9511     if (appData.debugMode) 
9512         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9513
9514     loadFlag = 0; /* [HGM] true game starts */
9515     return TRUE;
9516 }
9517
9518 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9519 int
9520 ReloadPosition(offset)
9521      int offset;
9522 {
9523     int positionNumber = lastLoadPositionNumber + offset;
9524     if (lastLoadPositionFP == NULL) {
9525         DisplayError(_("No position has been loaded yet"), 0);
9526         return FALSE;
9527     }
9528     if (positionNumber <= 0) {
9529         DisplayError(_("Can't back up any further"), 0);
9530         return FALSE;
9531     }
9532     return LoadPosition(lastLoadPositionFP, positionNumber,
9533                         lastLoadPositionTitle);
9534 }
9535
9536 /* Load the nth position from the given file */
9537 int
9538 LoadPositionFromFile(filename, n, title)
9539      char *filename;
9540      int n;
9541      char *title;
9542 {
9543     FILE *f;
9544     char buf[MSG_SIZ];
9545
9546     if (strcmp(filename, "-") == 0) {
9547         return LoadPosition(stdin, n, "stdin");
9548     } else {
9549         f = fopen(filename, "rb");
9550         if (f == NULL) {
9551             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9552             DisplayError(buf, errno);
9553             return FALSE;
9554         } else {
9555             return LoadPosition(f, n, title);
9556         }
9557     }
9558 }
9559
9560 /* Load the nth position from the given open file, and close it */
9561 int
9562 LoadPosition(f, positionNumber, title)
9563      FILE *f;
9564      int positionNumber;
9565      char *title;
9566 {
9567     char *p, line[MSG_SIZ];
9568     Board initial_position;
9569     int i, j, fenMode, pn;
9570     
9571     if (gameMode == Training )
9572         SetTrainingModeOff();
9573
9574     if (gameMode != BeginningOfGame) {
9575         Reset(FALSE, TRUE);
9576     }
9577     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9578         fclose(lastLoadPositionFP);
9579     }
9580     if (positionNumber == 0) positionNumber = 1;
9581     lastLoadPositionFP = f;
9582     lastLoadPositionNumber = positionNumber;
9583     strcpy(lastLoadPositionTitle, title);
9584     if (first.pr == NoProc) {
9585       StartChessProgram(&first);
9586       InitChessProgram(&first, FALSE);
9587     }    
9588     pn = positionNumber;
9589     if (positionNumber < 0) {
9590         /* Negative position number means to seek to that byte offset */
9591         if (fseek(f, -positionNumber, 0) == -1) {
9592             DisplayError(_("Can't seek on position file"), 0);
9593             return FALSE;
9594         };
9595         pn = 1;
9596     } else {
9597         if (fseek(f, 0, 0) == -1) {
9598             if (f == lastLoadPositionFP ?
9599                 positionNumber == lastLoadPositionNumber + 1 :
9600                 positionNumber == 1) {
9601                 pn = 1;
9602             } else {
9603                 DisplayError(_("Can't seek on position file"), 0);
9604                 return FALSE;
9605             }
9606         }
9607     }
9608     /* See if this file is FEN or old-style xboard */
9609     if (fgets(line, MSG_SIZ, f) == NULL) {
9610         DisplayError(_("Position not found in file"), 0);
9611         return FALSE;
9612     }
9613     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9614     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9615
9616     if (pn >= 2) {
9617         if (fenMode || line[0] == '#') pn--;
9618         while (pn > 0) {
9619             /* skip positions before number pn */
9620             if (fgets(line, MSG_SIZ, f) == NULL) {
9621                 Reset(TRUE, TRUE);
9622                 DisplayError(_("Position not found in file"), 0);
9623                 return FALSE;
9624             }
9625             if (fenMode || line[0] == '#') pn--;
9626         }
9627     }
9628
9629     if (fenMode) {
9630         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9631             DisplayError(_("Bad FEN position in file"), 0);
9632             return FALSE;
9633         }
9634     } else {
9635         (void) fgets(line, MSG_SIZ, f);
9636         (void) fgets(line, MSG_SIZ, f);
9637     
9638         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9639             (void) fgets(line, MSG_SIZ, f);
9640             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9641                 if (*p == ' ')
9642                   continue;
9643                 initial_position[i][j++] = CharToPiece(*p);
9644             }
9645         }
9646     
9647         blackPlaysFirst = FALSE;
9648         if (!feof(f)) {
9649             (void) fgets(line, MSG_SIZ, f);
9650             if (strncmp(line, "black", strlen("black"))==0)
9651               blackPlaysFirst = TRUE;
9652         }
9653     }
9654     startedFromSetupPosition = TRUE;
9655     
9656     SendToProgram("force\n", &first);
9657     CopyBoard(boards[0], initial_position);
9658     if (blackPlaysFirst) {
9659         currentMove = forwardMostMove = backwardMostMove = 1;
9660         strcpy(moveList[0], "");
9661         strcpy(parseList[0], "");
9662         CopyBoard(boards[1], initial_position);
9663         DisplayMessage("", _("Black to play"));
9664     } else {
9665         currentMove = forwardMostMove = backwardMostMove = 0;
9666         DisplayMessage("", _("White to play"));
9667     }
9668           /* [HGM] copy FEN attributes as well */
9669           {   int i;
9670               initialRulePlies = FENrulePlies;
9671               epStatus[forwardMostMove] = FENepStatus;
9672               for( i=0; i< nrCastlingRights; i++ )
9673                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9674           }
9675     SendBoard(&first, forwardMostMove);
9676     if (appData.debugMode) {
9677 int i, j;
9678   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9679   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9680         fprintf(debugFP, "Load Position\n");
9681     }
9682
9683     if (positionNumber > 1) {
9684         sprintf(line, "%s %d", title, positionNumber);
9685         DisplayTitle(line);
9686     } else {
9687         DisplayTitle(title);
9688     }
9689     gameMode = EditGame;
9690     ModeHighlight();
9691     ResetClocks();
9692     timeRemaining[0][1] = whiteTimeRemaining;
9693     timeRemaining[1][1] = blackTimeRemaining;
9694     DrawPosition(FALSE, boards[currentMove]);
9695    
9696     return TRUE;
9697 }
9698
9699
9700 void
9701 CopyPlayerNameIntoFileName(dest, src)
9702      char **dest, *src;
9703 {
9704     while (*src != NULLCHAR && *src != ',') {
9705         if (*src == ' ') {
9706             *(*dest)++ = '_';
9707             src++;
9708         } else {
9709             *(*dest)++ = *src++;
9710         }
9711     }
9712 }
9713
9714 char *DefaultFileName(ext)
9715      char *ext;
9716 {
9717     static char def[MSG_SIZ];
9718     char *p;
9719
9720     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9721         p = def;
9722         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9723         *p++ = '-';
9724         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9725         *p++ = '.';
9726         strcpy(p, ext);
9727     } else {
9728         def[0] = NULLCHAR;
9729     }
9730     return def;
9731 }
9732
9733 /* Save the current game to the given file */
9734 int
9735 SaveGameToFile(filename, append)
9736      char *filename;
9737      int append;
9738 {
9739     FILE *f;
9740     char buf[MSG_SIZ];
9741
9742     if (strcmp(filename, "-") == 0) {
9743         return SaveGame(stdout, 0, NULL);
9744     } else {
9745         f = fopen(filename, append ? "a" : "w");
9746         if (f == NULL) {
9747             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9748             DisplayError(buf, errno);
9749             return FALSE;
9750         } else {
9751             return SaveGame(f, 0, NULL);
9752         }
9753     }
9754 }
9755
9756 char *
9757 SavePart(str)
9758      char *str;
9759 {
9760     static char buf[MSG_SIZ];
9761     char *p;
9762     
9763     p = strchr(str, ' ');
9764     if (p == NULL) return str;
9765     strncpy(buf, str, p - str);
9766     buf[p - str] = NULLCHAR;
9767     return buf;
9768 }
9769
9770 #define PGN_MAX_LINE 75
9771
9772 #define PGN_SIDE_WHITE  0
9773 #define PGN_SIDE_BLACK  1
9774
9775 /* [AS] */
9776 static int FindFirstMoveOutOfBook( int side )
9777 {
9778     int result = -1;
9779
9780     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9781         int index = backwardMostMove;
9782         int has_book_hit = 0;
9783
9784         if( (index % 2) != side ) {
9785             index++;
9786         }
9787
9788         while( index < forwardMostMove ) {
9789             /* Check to see if engine is in book */
9790             int depth = pvInfoList[index].depth;
9791             int score = pvInfoList[index].score;
9792             int in_book = 0;
9793
9794             if( depth <= 2 ) {
9795                 in_book = 1;
9796             }
9797             else if( score == 0 && depth == 63 ) {
9798                 in_book = 1; /* Zappa */
9799             }
9800             else if( score == 2 && depth == 99 ) {
9801                 in_book = 1; /* Abrok */
9802             }
9803
9804             has_book_hit += in_book;
9805
9806             if( ! in_book ) {
9807                 result = index;
9808
9809                 break;
9810             }
9811
9812             index += 2;
9813         }
9814     }
9815
9816     return result;
9817 }
9818
9819 /* [AS] */
9820 void GetOutOfBookInfo( char * buf )
9821 {
9822     int oob[2];
9823     int i;
9824     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9825
9826     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9827     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9828
9829     *buf = '\0';
9830
9831     if( oob[0] >= 0 || oob[1] >= 0 ) {
9832         for( i=0; i<2; i++ ) {
9833             int idx = oob[i];
9834
9835             if( idx >= 0 ) {
9836                 if( i > 0 && oob[0] >= 0 ) {
9837                     strcat( buf, "   " );
9838                 }
9839
9840                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9841                 sprintf( buf+strlen(buf), "%s%.2f", 
9842                     pvInfoList[idx].score >= 0 ? "+" : "",
9843                     pvInfoList[idx].score / 100.0 );
9844             }
9845         }
9846     }
9847 }
9848
9849 /* Save game in PGN style and close the file */
9850 int
9851 SaveGamePGN(f)
9852      FILE *f;
9853 {
9854     int i, offset, linelen, newblock;
9855     time_t tm;
9856 //    char *movetext;
9857     char numtext[32];
9858     int movelen, numlen, blank;
9859     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9860
9861     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9862     
9863     tm = time((time_t *) NULL);
9864     
9865     PrintPGNTags(f, &gameInfo);
9866     
9867     if (backwardMostMove > 0 || startedFromSetupPosition) {
9868         char *fen = PositionToFEN(backwardMostMove, NULL);
9869         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9870         fprintf(f, "\n{--------------\n");
9871         PrintPosition(f, backwardMostMove);
9872         fprintf(f, "--------------}\n");
9873         free(fen);
9874     }
9875     else {
9876         /* [AS] Out of book annotation */
9877         if( appData.saveOutOfBookInfo ) {
9878             char buf[64];
9879
9880             GetOutOfBookInfo( buf );
9881
9882             if( buf[0] != '\0' ) {
9883                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9884             }
9885         }
9886
9887         fprintf(f, "\n");
9888     }
9889
9890     i = backwardMostMove;
9891     linelen = 0;
9892     newblock = TRUE;
9893
9894     while (i < forwardMostMove) {
9895         /* Print comments preceding this move */
9896         if (commentList[i] != NULL) {
9897             if (linelen > 0) fprintf(f, "\n");
9898             fprintf(f, "{\n%s}\n", commentList[i]);
9899             linelen = 0;
9900             newblock = TRUE;
9901         }
9902
9903         /* Format move number */
9904         if ((i % 2) == 0) {
9905             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9906         } else {
9907             if (newblock) {
9908                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9909             } else {
9910                 numtext[0] = NULLCHAR;
9911             }
9912         }
9913         numlen = strlen(numtext);
9914         newblock = FALSE;
9915
9916         /* Print move number */
9917         blank = linelen > 0 && numlen > 0;
9918         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9919             fprintf(f, "\n");
9920             linelen = 0;
9921             blank = 0;
9922         }
9923         if (blank) {
9924             fprintf(f, " ");
9925             linelen++;
9926         }
9927         fprintf(f, "%s", numtext);
9928         linelen += numlen;
9929
9930         /* Get move */
9931         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9932         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9933
9934         /* Print move */
9935         blank = linelen > 0 && movelen > 0;
9936         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9937             fprintf(f, "\n");
9938             linelen = 0;
9939             blank = 0;
9940         }
9941         if (blank) {
9942             fprintf(f, " ");
9943             linelen++;
9944         }
9945         fprintf(f, "%s", move_buffer);
9946         linelen += movelen;
9947
9948         /* [AS] Add PV info if present */
9949         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9950             /* [HGM] add time */
9951             char buf[MSG_SIZ]; int seconds = 0;
9952
9953             if(i >= backwardMostMove) {
9954                 if(WhiteOnMove(i))
9955                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9956                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9957                 else
9958                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9959                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9960             }
9961             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9962
9963             if( seconds <= 0) buf[0] = 0; else
9964             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9965                 seconds = (seconds + 4)/10; // round to full seconds
9966                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9967                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9968             }
9969
9970             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9971                 pvInfoList[i].score >= 0 ? "+" : "",
9972                 pvInfoList[i].score / 100.0,
9973                 pvInfoList[i].depth,
9974                 buf );
9975
9976             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9977
9978             /* Print score/depth */
9979             blank = linelen > 0 && movelen > 0;
9980             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9981                 fprintf(f, "\n");
9982                 linelen = 0;
9983                 blank = 0;
9984             }
9985             if (blank) {
9986                 fprintf(f, " ");
9987                 linelen++;
9988             }
9989             fprintf(f, "%s", move_buffer);
9990             linelen += movelen;
9991         }
9992
9993         i++;
9994     }
9995     
9996     /* Start a new line */
9997     if (linelen > 0) fprintf(f, "\n");
9998
9999     /* Print comments after last move */
10000     if (commentList[i] != NULL) {
10001         fprintf(f, "{\n%s}\n", commentList[i]);
10002     }
10003
10004     /* Print result */
10005     if (gameInfo.resultDetails != NULL &&
10006         gameInfo.resultDetails[0] != NULLCHAR) {
10007         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10008                 PGNResult(gameInfo.result));
10009     } else {
10010         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10011     }
10012
10013     fclose(f);
10014     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10015     return TRUE;
10016 }
10017
10018 /* Save game in old style and close the file */
10019 int
10020 SaveGameOldStyle(f)
10021      FILE *f;
10022 {
10023     int i, offset;
10024     time_t tm;
10025     
10026     tm = time((time_t *) NULL);
10027     
10028     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10029     PrintOpponents(f);
10030     
10031     if (backwardMostMove > 0 || startedFromSetupPosition) {
10032         fprintf(f, "\n[--------------\n");
10033         PrintPosition(f, backwardMostMove);
10034         fprintf(f, "--------------]\n");
10035     } else {
10036         fprintf(f, "\n");
10037     }
10038
10039     i = backwardMostMove;
10040     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10041
10042     while (i < forwardMostMove) {
10043         if (commentList[i] != NULL) {
10044             fprintf(f, "[%s]\n", commentList[i]);
10045         }
10046
10047         if ((i % 2) == 1) {
10048             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10049             i++;
10050         } else {
10051             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10052             i++;
10053             if (commentList[i] != NULL) {
10054                 fprintf(f, "\n");
10055                 continue;
10056             }
10057             if (i >= forwardMostMove) {
10058                 fprintf(f, "\n");
10059                 break;
10060             }
10061             fprintf(f, "%s\n", parseList[i]);
10062             i++;
10063         }
10064     }
10065     
10066     if (commentList[i] != NULL) {
10067         fprintf(f, "[%s]\n", commentList[i]);
10068     }
10069
10070     /* This isn't really the old style, but it's close enough */
10071     if (gameInfo.resultDetails != NULL &&
10072         gameInfo.resultDetails[0] != NULLCHAR) {
10073         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10074                 gameInfo.resultDetails);
10075     } else {
10076         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10077     }
10078
10079     fclose(f);
10080     return TRUE;
10081 }
10082
10083 /* Save the current game to open file f and close the file */
10084 int
10085 SaveGame(f, dummy, dummy2)
10086      FILE *f;
10087      int dummy;
10088      char *dummy2;
10089 {
10090     if (gameMode == EditPosition) EditPositionDone();
10091     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10092     if (appData.oldSaveStyle)
10093       return SaveGameOldStyle(f);
10094     else
10095       return SaveGamePGN(f);
10096 }
10097
10098 /* Save the current position to the given file */
10099 int
10100 SavePositionToFile(filename)
10101      char *filename;
10102 {
10103     FILE *f;
10104     char buf[MSG_SIZ];
10105
10106     if (strcmp(filename, "-") == 0) {
10107         return SavePosition(stdout, 0, NULL);
10108     } else {
10109         f = fopen(filename, "a");
10110         if (f == NULL) {
10111             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10112             DisplayError(buf, errno);
10113             return FALSE;
10114         } else {
10115             SavePosition(f, 0, NULL);
10116             return TRUE;
10117         }
10118     }
10119 }
10120
10121 /* Save the current position to the given open file and close the file */
10122 int
10123 SavePosition(f, dummy, dummy2)
10124      FILE *f;
10125      int dummy;
10126      char *dummy2;
10127 {
10128     time_t tm;
10129     char *fen;
10130     
10131     if (appData.oldSaveStyle) {
10132         tm = time((time_t *) NULL);
10133     
10134         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10135         PrintOpponents(f);
10136         fprintf(f, "[--------------\n");
10137         PrintPosition(f, currentMove);
10138         fprintf(f, "--------------]\n");
10139     } else {
10140         fen = PositionToFEN(currentMove, NULL);
10141         fprintf(f, "%s\n", fen);
10142         free(fen);
10143     }
10144     fclose(f);
10145     return TRUE;
10146 }
10147
10148 void
10149 ReloadCmailMsgEvent(unregister)
10150      int unregister;
10151 {
10152 #if !WIN32
10153     static char *inFilename = NULL;
10154     static char *outFilename;
10155     int i;
10156     struct stat inbuf, outbuf;
10157     int status;
10158     
10159     /* Any registered moves are unregistered if unregister is set, */
10160     /* i.e. invoked by the signal handler */
10161     if (unregister) {
10162         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10163             cmailMoveRegistered[i] = FALSE;
10164             if (cmailCommentList[i] != NULL) {
10165                 free(cmailCommentList[i]);
10166                 cmailCommentList[i] = NULL;
10167             }
10168         }
10169         nCmailMovesRegistered = 0;
10170     }
10171
10172     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10173         cmailResult[i] = CMAIL_NOT_RESULT;
10174     }
10175     nCmailResults = 0;
10176
10177     if (inFilename == NULL) {
10178         /* Because the filenames are static they only get malloced once  */
10179         /* and they never get freed                                      */
10180         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10181         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10182
10183         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10184         sprintf(outFilename, "%s.out", appData.cmailGameName);
10185     }
10186     
10187     status = stat(outFilename, &outbuf);
10188     if (status < 0) {
10189         cmailMailedMove = FALSE;
10190     } else {
10191         status = stat(inFilename, &inbuf);
10192         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10193     }
10194     
10195     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10196        counts the games, notes how each one terminated, etc.
10197        
10198        It would be nice to remove this kludge and instead gather all
10199        the information while building the game list.  (And to keep it
10200        in the game list nodes instead of having a bunch of fixed-size
10201        parallel arrays.)  Note this will require getting each game's
10202        termination from the PGN tags, as the game list builder does
10203        not process the game moves.  --mann
10204        */
10205     cmailMsgLoaded = TRUE;
10206     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10207     
10208     /* Load first game in the file or popup game menu */
10209     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10210
10211 #endif /* !WIN32 */
10212     return;
10213 }
10214
10215 int
10216 RegisterMove()
10217 {
10218     FILE *f;
10219     char string[MSG_SIZ];
10220
10221     if (   cmailMailedMove
10222         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10223         return TRUE;            /* Allow free viewing  */
10224     }
10225
10226     /* Unregister move to ensure that we don't leave RegisterMove        */
10227     /* with the move registered when the conditions for registering no   */
10228     /* longer hold                                                       */
10229     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10230         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10231         nCmailMovesRegistered --;
10232
10233         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10234           {
10235               free(cmailCommentList[lastLoadGameNumber - 1]);
10236               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10237           }
10238     }
10239
10240     if (cmailOldMove == -1) {
10241         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10242         return FALSE;
10243     }
10244
10245     if (currentMove > cmailOldMove + 1) {
10246         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10247         return FALSE;
10248     }
10249
10250     if (currentMove < cmailOldMove) {
10251         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10252         return FALSE;
10253     }
10254
10255     if (forwardMostMove > currentMove) {
10256         /* Silently truncate extra moves */
10257         TruncateGame();
10258     }
10259
10260     if (   (currentMove == cmailOldMove + 1)
10261         || (   (currentMove == cmailOldMove)
10262             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10263                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10264         if (gameInfo.result != GameUnfinished) {
10265             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10266         }
10267
10268         if (commentList[currentMove] != NULL) {
10269             cmailCommentList[lastLoadGameNumber - 1]
10270               = StrSave(commentList[currentMove]);
10271         }
10272         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10273
10274         if (appData.debugMode)
10275           fprintf(debugFP, "Saving %s for game %d\n",
10276                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10277
10278         sprintf(string,
10279                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10280         
10281         f = fopen(string, "w");
10282         if (appData.oldSaveStyle) {
10283             SaveGameOldStyle(f); /* also closes the file */
10284             
10285             sprintf(string, "%s.pos.out", appData.cmailGameName);
10286             f = fopen(string, "w");
10287             SavePosition(f, 0, NULL); /* also closes the file */
10288         } else {
10289             fprintf(f, "{--------------\n");
10290             PrintPosition(f, currentMove);
10291             fprintf(f, "--------------}\n\n");
10292             
10293             SaveGame(f, 0, NULL); /* also closes the file*/
10294         }
10295         
10296         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10297         nCmailMovesRegistered ++;
10298     } else if (nCmailGames == 1) {
10299         DisplayError(_("You have not made a move yet"), 0);
10300         return FALSE;
10301     }
10302
10303     return TRUE;
10304 }
10305
10306 void
10307 MailMoveEvent()
10308 {
10309 #if !WIN32
10310     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10311     FILE *commandOutput;
10312     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10313     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10314     int nBuffers;
10315     int i;
10316     int archived;
10317     char *arcDir;
10318
10319     if (! cmailMsgLoaded) {
10320         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10321         return;
10322     }
10323
10324     if (nCmailGames == nCmailResults) {
10325         DisplayError(_("No unfinished games"), 0);
10326         return;
10327     }
10328
10329 #if CMAIL_PROHIBIT_REMAIL
10330     if (cmailMailedMove) {
10331         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);
10332         DisplayError(msg, 0);
10333         return;
10334     }
10335 #endif
10336
10337     if (! (cmailMailedMove || RegisterMove())) return;
10338     
10339     if (   cmailMailedMove
10340         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10341         sprintf(string, partCommandString,
10342                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10343         commandOutput = popen(string, "r");
10344
10345         if (commandOutput == NULL) {
10346             DisplayError(_("Failed to invoke cmail"), 0);
10347         } else {
10348             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10349                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10350             }
10351             if (nBuffers > 1) {
10352                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10353                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10354                 nBytes = MSG_SIZ - 1;
10355             } else {
10356                 (void) memcpy(msg, buffer, nBytes);
10357             }
10358             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10359
10360             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10361                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10362
10363                 archived = TRUE;
10364                 for (i = 0; i < nCmailGames; i ++) {
10365                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10366                         archived = FALSE;
10367                     }
10368                 }
10369                 if (   archived
10370                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10371                         != NULL)) {
10372                     sprintf(buffer, "%s/%s.%s.archive",
10373                             arcDir,
10374                             appData.cmailGameName,
10375                             gameInfo.date);
10376                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10377                     cmailMsgLoaded = FALSE;
10378                 }
10379             }
10380
10381             DisplayInformation(msg);
10382             pclose(commandOutput);
10383         }
10384     } else {
10385         if ((*cmailMsg) != '\0') {
10386             DisplayInformation(cmailMsg);
10387         }
10388     }
10389
10390     return;
10391 #endif /* !WIN32 */
10392 }
10393
10394 char *
10395 CmailMsg()
10396 {
10397 #if WIN32
10398     return NULL;
10399 #else
10400     int  prependComma = 0;
10401     char number[5];
10402     char string[MSG_SIZ];       /* Space for game-list */
10403     int  i;
10404     
10405     if (!cmailMsgLoaded) return "";
10406
10407     if (cmailMailedMove) {
10408         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10409     } else {
10410         /* Create a list of games left */
10411         sprintf(string, "[");
10412         for (i = 0; i < nCmailGames; i ++) {
10413             if (! (   cmailMoveRegistered[i]
10414                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10415                 if (prependComma) {
10416                     sprintf(number, ",%d", i + 1);
10417                 } else {
10418                     sprintf(number, "%d", i + 1);
10419                     prependComma = 1;
10420                 }
10421                 
10422                 strcat(string, number);
10423             }
10424         }
10425         strcat(string, "]");
10426
10427         if (nCmailMovesRegistered + nCmailResults == 0) {
10428             switch (nCmailGames) {
10429               case 1:
10430                 sprintf(cmailMsg,
10431                         _("Still need to make move for game\n"));
10432                 break;
10433                 
10434               case 2:
10435                 sprintf(cmailMsg,
10436                         _("Still need to make moves for both games\n"));
10437                 break;
10438                 
10439               default:
10440                 sprintf(cmailMsg,
10441                         _("Still need to make moves for all %d games\n"),
10442                         nCmailGames);
10443                 break;
10444             }
10445         } else {
10446             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10447               case 1:
10448                 sprintf(cmailMsg,
10449                         _("Still need to make a move for game %s\n"),
10450                         string);
10451                 break;
10452                 
10453               case 0:
10454                 if (nCmailResults == nCmailGames) {
10455                     sprintf(cmailMsg, _("No unfinished games\n"));
10456                 } else {
10457                     sprintf(cmailMsg, _("Ready to send mail\n"));
10458                 }
10459                 break;
10460                 
10461               default:
10462                 sprintf(cmailMsg,
10463                         _("Still need to make moves for games %s\n"),
10464                         string);
10465             }
10466         }
10467     }
10468     return cmailMsg;
10469 #endif /* WIN32 */
10470 }
10471
10472 void
10473 ResetGameEvent()
10474 {
10475     if (gameMode == Training)
10476       SetTrainingModeOff();
10477
10478     Reset(TRUE, TRUE);
10479     cmailMsgLoaded = FALSE;
10480     if (appData.icsActive) {
10481       SendToICS(ics_prefix);
10482       SendToICS("refresh\n");
10483     }
10484 }
10485
10486 void
10487 ExitEvent(status)
10488      int status;
10489 {
10490     exiting++;
10491     if (exiting > 2) {
10492       /* Give up on clean exit */
10493       exit(status);
10494     }
10495     if (exiting > 1) {
10496       /* Keep trying for clean exit */
10497       return;
10498     }
10499
10500     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10501
10502     if (telnetISR != NULL) {
10503       RemoveInputSource(telnetISR);
10504     }
10505     if (icsPR != NoProc) {
10506       DestroyChildProcess(icsPR, TRUE);
10507     }
10508
10509     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10510     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10511
10512     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10513     /* make sure this other one finishes before killing it!                  */
10514     if(endingGame) { int count = 0;
10515         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10516         while(endingGame && count++ < 10) DoSleep(1);
10517         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10518     }
10519
10520     /* Kill off chess programs */
10521     if (first.pr != NoProc) {
10522         ExitAnalyzeMode();
10523         
10524         DoSleep( appData.delayBeforeQuit );
10525         SendToProgram("quit\n", &first);
10526         DoSleep( appData.delayAfterQuit );
10527         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10528     }
10529     if (second.pr != NoProc) {
10530         DoSleep( appData.delayBeforeQuit );
10531         SendToProgram("quit\n", &second);
10532         DoSleep( appData.delayAfterQuit );
10533         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10534     }
10535     if (first.isr != NULL) {
10536         RemoveInputSource(first.isr);
10537     }
10538     if (second.isr != NULL) {
10539         RemoveInputSource(second.isr);
10540     }
10541
10542     ShutDownFrontEnd();
10543     exit(status);
10544 }
10545
10546 void
10547 PauseEvent()
10548 {
10549     if (appData.debugMode)
10550         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10551     if (pausing) {
10552         pausing = FALSE;
10553         ModeHighlight();
10554         if (gameMode == MachinePlaysWhite ||
10555             gameMode == MachinePlaysBlack) {
10556             StartClocks();
10557         } else {
10558             DisplayBothClocks();
10559         }
10560         if (gameMode == PlayFromGameFile) {
10561             if (appData.timeDelay >= 0) 
10562                 AutoPlayGameLoop();
10563         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10564             Reset(FALSE, TRUE);
10565             SendToICS(ics_prefix);
10566             SendToICS("refresh\n");
10567         } else if (currentMove < forwardMostMove) {
10568             ForwardInner(forwardMostMove);
10569         }
10570         pauseExamInvalid = FALSE;
10571     } else {
10572         switch (gameMode) {
10573           default:
10574             return;
10575           case IcsExamining:
10576             pauseExamForwardMostMove = forwardMostMove;
10577             pauseExamInvalid = FALSE;
10578             /* fall through */
10579           case IcsObserving:
10580           case IcsPlayingWhite:
10581           case IcsPlayingBlack:
10582             pausing = TRUE;
10583             ModeHighlight();
10584             return;
10585           case PlayFromGameFile:
10586             (void) StopLoadGameTimer();
10587             pausing = TRUE;
10588             ModeHighlight();
10589             break;
10590           case BeginningOfGame:
10591             if (appData.icsActive) return;
10592             /* else fall through */
10593           case MachinePlaysWhite:
10594           case MachinePlaysBlack:
10595           case TwoMachinesPlay:
10596             if (forwardMostMove == 0)
10597               return;           /* don't pause if no one has moved */
10598             if ((gameMode == MachinePlaysWhite &&
10599                  !WhiteOnMove(forwardMostMove)) ||
10600                 (gameMode == MachinePlaysBlack &&
10601                  WhiteOnMove(forwardMostMove))) {
10602                 StopClocks();
10603             }
10604             pausing = TRUE;
10605             ModeHighlight();
10606             break;
10607         }
10608     }
10609 }
10610
10611 void
10612 EditCommentEvent()
10613 {
10614     char title[MSG_SIZ];
10615
10616     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10617         strcpy(title, _("Edit comment"));
10618     } else {
10619         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10620                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10621                 parseList[currentMove - 1]);
10622     }
10623
10624     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10625 }
10626
10627
10628 void
10629 EditTagsEvent()
10630 {
10631     char *tags = PGNTags(&gameInfo);
10632     EditTagsPopUp(tags);
10633     free(tags);
10634 }
10635
10636 void
10637 AnalyzeModeEvent()
10638 {
10639     if (appData.noChessProgram || gameMode == AnalyzeMode)
10640       return;
10641
10642     if (gameMode != AnalyzeFile) {
10643         if (!appData.icsEngineAnalyze) {
10644                EditGameEvent();
10645                if (gameMode != EditGame) return;
10646         }
10647         ResurrectChessProgram();
10648         SendToProgram("analyze\n", &first);
10649         first.analyzing = TRUE;
10650         /*first.maybeThinking = TRUE;*/
10651         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10652         EngineOutputPopUp();
10653     }
10654     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10655     pausing = FALSE;
10656     ModeHighlight();
10657     SetGameInfo();
10658
10659     StartAnalysisClock();
10660     GetTimeMark(&lastNodeCountTime);
10661     lastNodeCount = 0;
10662 }
10663
10664 void
10665 AnalyzeFileEvent()
10666 {
10667     if (appData.noChessProgram || gameMode == AnalyzeFile)
10668       return;
10669
10670     if (gameMode != AnalyzeMode) {
10671         EditGameEvent();
10672         if (gameMode != EditGame) return;
10673         ResurrectChessProgram();
10674         SendToProgram("analyze\n", &first);
10675         first.analyzing = TRUE;
10676         /*first.maybeThinking = TRUE;*/
10677         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10678         EngineOutputPopUp();
10679     }
10680     gameMode = AnalyzeFile;
10681     pausing = FALSE;
10682     ModeHighlight();
10683     SetGameInfo();
10684
10685     StartAnalysisClock();
10686     GetTimeMark(&lastNodeCountTime);
10687     lastNodeCount = 0;
10688 }
10689
10690 void
10691 MachineWhiteEvent()
10692 {
10693     char buf[MSG_SIZ];
10694     char *bookHit = NULL;
10695
10696     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10697       return;
10698
10699
10700     if (gameMode == PlayFromGameFile || 
10701         gameMode == TwoMachinesPlay  || 
10702         gameMode == Training         || 
10703         gameMode == AnalyzeMode      || 
10704         gameMode == EndOfGame)
10705         EditGameEvent();
10706
10707     if (gameMode == EditPosition) 
10708         EditPositionDone();
10709
10710     if (!WhiteOnMove(currentMove)) {
10711         DisplayError(_("It is not White's turn"), 0);
10712         return;
10713     }
10714   
10715     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10716       ExitAnalyzeMode();
10717
10718     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10719         gameMode == AnalyzeFile)
10720         TruncateGame();
10721
10722     ResurrectChessProgram();    /* in case it isn't running */
10723     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10724         gameMode = MachinePlaysWhite;
10725         ResetClocks();
10726     } else
10727     gameMode = MachinePlaysWhite;
10728     pausing = FALSE;
10729     ModeHighlight();
10730     SetGameInfo();
10731     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10732     DisplayTitle(buf);
10733     if (first.sendName) {
10734       sprintf(buf, "name %s\n", gameInfo.black);
10735       SendToProgram(buf, &first);
10736     }
10737     if (first.sendTime) {
10738       if (first.useColors) {
10739         SendToProgram("black\n", &first); /*gnu kludge*/
10740       }
10741       SendTimeRemaining(&first, TRUE);
10742     }
10743     if (first.useColors) {
10744       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10745     }
10746     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10747     SetMachineThinkingEnables();
10748     first.maybeThinking = TRUE;
10749     StartClocks();
10750     firstMove = FALSE;
10751
10752     if (appData.autoFlipView && !flipView) {
10753       flipView = !flipView;
10754       DrawPosition(FALSE, NULL);
10755       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10756     }
10757
10758     if(bookHit) { // [HGM] book: simulate book reply
10759         static char bookMove[MSG_SIZ]; // a bit generous?
10760
10761         programStats.nodes = programStats.depth = programStats.time = 
10762         programStats.score = programStats.got_only_move = 0;
10763         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10764
10765         strcpy(bookMove, "move ");
10766         strcat(bookMove, bookHit);
10767         HandleMachineMove(bookMove, &first);
10768     }
10769 }
10770
10771 void
10772 MachineBlackEvent()
10773 {
10774     char buf[MSG_SIZ];
10775    char *bookHit = NULL;
10776
10777     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10778         return;
10779
10780
10781     if (gameMode == PlayFromGameFile || 
10782         gameMode == TwoMachinesPlay  || 
10783         gameMode == Training         || 
10784         gameMode == AnalyzeMode      || 
10785         gameMode == EndOfGame)
10786         EditGameEvent();
10787
10788     if (gameMode == EditPosition) 
10789         EditPositionDone();
10790
10791     if (WhiteOnMove(currentMove)) {
10792         DisplayError(_("It is not Black's turn"), 0);
10793         return;
10794     }
10795     
10796     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10797       ExitAnalyzeMode();
10798
10799     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10800         gameMode == AnalyzeFile)
10801         TruncateGame();
10802
10803     ResurrectChessProgram();    /* in case it isn't running */
10804     gameMode = MachinePlaysBlack;
10805     pausing = FALSE;
10806     ModeHighlight();
10807     SetGameInfo();
10808     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10809     DisplayTitle(buf);
10810     if (first.sendName) {
10811       sprintf(buf, "name %s\n", gameInfo.white);
10812       SendToProgram(buf, &first);
10813     }
10814     if (first.sendTime) {
10815       if (first.useColors) {
10816         SendToProgram("white\n", &first); /*gnu kludge*/
10817       }
10818       SendTimeRemaining(&first, FALSE);
10819     }
10820     if (first.useColors) {
10821       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10822     }
10823     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10824     SetMachineThinkingEnables();
10825     first.maybeThinking = TRUE;
10826     StartClocks();
10827
10828     if (appData.autoFlipView && flipView) {
10829       flipView = !flipView;
10830       DrawPosition(FALSE, NULL);
10831       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10832     }
10833     if(bookHit) { // [HGM] book: simulate book reply
10834         static char bookMove[MSG_SIZ]; // a bit generous?
10835
10836         programStats.nodes = programStats.depth = programStats.time = 
10837         programStats.score = programStats.got_only_move = 0;
10838         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10839
10840         strcpy(bookMove, "move ");
10841         strcat(bookMove, bookHit);
10842         HandleMachineMove(bookMove, &first);
10843     }
10844 }
10845
10846
10847 void
10848 DisplayTwoMachinesTitle()
10849 {
10850     char buf[MSG_SIZ];
10851     if (appData.matchGames > 0) {
10852         if (first.twoMachinesColor[0] == 'w') {
10853             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10854                     gameInfo.white, gameInfo.black,
10855                     first.matchWins, second.matchWins,
10856                     matchGame - 1 - (first.matchWins + second.matchWins));
10857         } else {
10858             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10859                     gameInfo.white, gameInfo.black,
10860                     second.matchWins, first.matchWins,
10861                     matchGame - 1 - (first.matchWins + second.matchWins));
10862         }
10863     } else {
10864         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10865     }
10866     DisplayTitle(buf);
10867 }
10868
10869 void
10870 TwoMachinesEvent P((void))
10871 {
10872     int i;
10873     char buf[MSG_SIZ];
10874     ChessProgramState *onmove;
10875     char *bookHit = NULL;
10876     
10877     if (appData.noChessProgram) return;
10878
10879     switch (gameMode) {
10880       case TwoMachinesPlay:
10881         return;
10882       case MachinePlaysWhite:
10883       case MachinePlaysBlack:
10884         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10885             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10886             return;
10887         }
10888         /* fall through */
10889       case BeginningOfGame:
10890       case PlayFromGameFile:
10891       case EndOfGame:
10892         EditGameEvent();
10893         if (gameMode != EditGame) return;
10894         break;
10895       case EditPosition:
10896         EditPositionDone();
10897         break;
10898       case AnalyzeMode:
10899       case AnalyzeFile:
10900         ExitAnalyzeMode();
10901         break;
10902       case EditGame:
10903       default:
10904         break;
10905     }
10906
10907     forwardMostMove = currentMove;
10908     ResurrectChessProgram();    /* in case first program isn't running */
10909
10910     if (second.pr == NULL) {
10911         StartChessProgram(&second);
10912         if (second.protocolVersion == 1) {
10913           TwoMachinesEventIfReady();
10914         } else {
10915           /* kludge: allow timeout for initial "feature" command */
10916           FreezeUI();
10917           DisplayMessage("", _("Starting second chess program"));
10918           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10919         }
10920         return;
10921     }
10922     DisplayMessage("", "");
10923     InitChessProgram(&second, FALSE);
10924     SendToProgram("force\n", &second);
10925     if (startedFromSetupPosition) {
10926         SendBoard(&second, backwardMostMove);
10927     if (appData.debugMode) {
10928         fprintf(debugFP, "Two Machines\n");
10929     }
10930     }
10931     for (i = backwardMostMove; i < forwardMostMove; i++) {
10932         SendMoveToProgram(i, &second);
10933     }
10934
10935     gameMode = TwoMachinesPlay;
10936     pausing = FALSE;
10937     ModeHighlight();
10938     SetGameInfo();
10939     DisplayTwoMachinesTitle();
10940     firstMove = TRUE;
10941     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10942         onmove = &first;
10943     } else {
10944         onmove = &second;
10945     }
10946
10947     SendToProgram(first.computerString, &first);
10948     if (first.sendName) {
10949       sprintf(buf, "name %s\n", second.tidy);
10950       SendToProgram(buf, &first);
10951     }
10952     SendToProgram(second.computerString, &second);
10953     if (second.sendName) {
10954       sprintf(buf, "name %s\n", first.tidy);
10955       SendToProgram(buf, &second);
10956     }
10957
10958     ResetClocks();
10959     if (!first.sendTime || !second.sendTime) {
10960         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10961         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10962     }
10963     if (onmove->sendTime) {
10964       if (onmove->useColors) {
10965         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10966       }
10967       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10968     }
10969     if (onmove->useColors) {
10970       SendToProgram(onmove->twoMachinesColor, onmove);
10971     }
10972     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10973 //    SendToProgram("go\n", onmove);
10974     onmove->maybeThinking = TRUE;
10975     SetMachineThinkingEnables();
10976
10977     StartClocks();
10978
10979     if(bookHit) { // [HGM] book: simulate book reply
10980         static char bookMove[MSG_SIZ]; // a bit generous?
10981
10982         programStats.nodes = programStats.depth = programStats.time = 
10983         programStats.score = programStats.got_only_move = 0;
10984         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10985
10986         strcpy(bookMove, "move ");
10987         strcat(bookMove, bookHit);
10988         savedMessage = bookMove; // args for deferred call
10989         savedState = onmove;
10990         ScheduleDelayedEvent(DeferredBookMove, 1);
10991     }
10992 }
10993
10994 void
10995 TrainingEvent()
10996 {
10997     if (gameMode == Training) {
10998       SetTrainingModeOff();
10999       gameMode = PlayFromGameFile;
11000       DisplayMessage("", _("Training mode off"));
11001     } else {
11002       gameMode = Training;
11003       animateTraining = appData.animate;
11004
11005       /* make sure we are not already at the end of the game */
11006       if (currentMove < forwardMostMove) {
11007         SetTrainingModeOn();
11008         DisplayMessage("", _("Training mode on"));
11009       } else {
11010         gameMode = PlayFromGameFile;
11011         DisplayError(_("Already at end of game"), 0);
11012       }
11013     }
11014     ModeHighlight();
11015 }
11016
11017 void
11018 IcsClientEvent()
11019 {
11020     if (!appData.icsActive) return;
11021     switch (gameMode) {
11022       case IcsPlayingWhite:
11023       case IcsPlayingBlack:
11024       case IcsObserving:
11025       case IcsIdle:
11026       case BeginningOfGame:
11027       case IcsExamining:
11028         return;
11029
11030       case EditGame:
11031         break;
11032
11033       case EditPosition:
11034         EditPositionDone();
11035         break;
11036
11037       case AnalyzeMode:
11038       case AnalyzeFile:
11039         ExitAnalyzeMode();
11040         break;
11041         
11042       default:
11043         EditGameEvent();
11044         break;
11045     }
11046
11047     gameMode = IcsIdle;
11048     ModeHighlight();
11049     return;
11050 }
11051
11052
11053 void
11054 EditGameEvent()
11055 {
11056     int i;
11057
11058     switch (gameMode) {
11059       case Training:
11060         SetTrainingModeOff();
11061         break;
11062       case MachinePlaysWhite:
11063       case MachinePlaysBlack:
11064       case BeginningOfGame:
11065         SendToProgram("force\n", &first);
11066         SetUserThinkingEnables();
11067         break;
11068       case PlayFromGameFile:
11069         (void) StopLoadGameTimer();
11070         if (gameFileFP != NULL) {
11071             gameFileFP = NULL;
11072         }
11073         break;
11074       case EditPosition:
11075         EditPositionDone();
11076         break;
11077       case AnalyzeMode:
11078       case AnalyzeFile:
11079         ExitAnalyzeMode();
11080         SendToProgram("force\n", &first);
11081         break;
11082       case TwoMachinesPlay:
11083         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11084         ResurrectChessProgram();
11085         SetUserThinkingEnables();
11086         break;
11087       case EndOfGame:
11088         ResurrectChessProgram();
11089         break;
11090       case IcsPlayingBlack:
11091       case IcsPlayingWhite:
11092         DisplayError(_("Warning: You are still playing a game"), 0);
11093         break;
11094       case IcsObserving:
11095         DisplayError(_("Warning: You are still observing a game"), 0);
11096         break;
11097       case IcsExamining:
11098         DisplayError(_("Warning: You are still examining a game"), 0);
11099         break;
11100       case IcsIdle:
11101         break;
11102       case EditGame:
11103       default:
11104         return;
11105     }
11106     
11107     pausing = FALSE;
11108     StopClocks();
11109     first.offeredDraw = second.offeredDraw = 0;
11110
11111     if (gameMode == PlayFromGameFile) {
11112         whiteTimeRemaining = timeRemaining[0][currentMove];
11113         blackTimeRemaining = timeRemaining[1][currentMove];
11114         DisplayTitle("");
11115     }
11116
11117     if (gameMode == MachinePlaysWhite ||
11118         gameMode == MachinePlaysBlack ||
11119         gameMode == TwoMachinesPlay ||
11120         gameMode == EndOfGame) {
11121         i = forwardMostMove;
11122         while (i > currentMove) {
11123             SendToProgram("undo\n", &first);
11124             i--;
11125         }
11126         whiteTimeRemaining = timeRemaining[0][currentMove];
11127         blackTimeRemaining = timeRemaining[1][currentMove];
11128         DisplayBothClocks();
11129         if (whiteFlag || blackFlag) {
11130             whiteFlag = blackFlag = 0;
11131         }
11132         DisplayTitle("");
11133     }           
11134     
11135     gameMode = EditGame;
11136     ModeHighlight();
11137     SetGameInfo();
11138 }
11139
11140
11141 void
11142 EditPositionEvent()
11143 {
11144     if (gameMode == EditPosition) {
11145         EditGameEvent();
11146         return;
11147     }
11148     
11149     EditGameEvent();
11150     if (gameMode != EditGame) return;
11151     
11152     gameMode = EditPosition;
11153     ModeHighlight();
11154     SetGameInfo();
11155     if (currentMove > 0)
11156       CopyBoard(boards[0], boards[currentMove]);
11157     
11158     blackPlaysFirst = !WhiteOnMove(currentMove);
11159     ResetClocks();
11160     currentMove = forwardMostMove = backwardMostMove = 0;
11161     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11162     DisplayMove(-1);
11163 }
11164
11165 void
11166 ExitAnalyzeMode()
11167 {
11168     /* [DM] icsEngineAnalyze - possible call from other functions */
11169     if (appData.icsEngineAnalyze) {
11170         appData.icsEngineAnalyze = FALSE;
11171
11172         DisplayMessage("",_("Close ICS engine analyze..."));
11173     }
11174     if (first.analysisSupport && first.analyzing) {
11175       SendToProgram("exit\n", &first);
11176       first.analyzing = FALSE;
11177     }
11178     thinkOutput[0] = NULLCHAR;
11179 }
11180
11181 void
11182 EditPositionDone()
11183 {
11184     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11185
11186     startedFromSetupPosition = TRUE;
11187     InitChessProgram(&first, FALSE);
11188     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11189     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11190         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11191         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11192     } else castlingRights[0][2] = -1;
11193     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11194         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11195         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11196     } else castlingRights[0][5] = -1;
11197     SendToProgram("force\n", &first);
11198     if (blackPlaysFirst) {
11199         strcpy(moveList[0], "");
11200         strcpy(parseList[0], "");
11201         currentMove = forwardMostMove = backwardMostMove = 1;
11202         CopyBoard(boards[1], boards[0]);
11203         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11204         { int i;
11205           epStatus[1] = epStatus[0];
11206           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11207         }
11208     } else {
11209         currentMove = forwardMostMove = backwardMostMove = 0;
11210     }
11211     SendBoard(&first, forwardMostMove);
11212     if (appData.debugMode) {
11213         fprintf(debugFP, "EditPosDone\n");
11214     }
11215     DisplayTitle("");
11216     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11217     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11218     gameMode = EditGame;
11219     ModeHighlight();
11220     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11221     ClearHighlights(); /* [AS] */
11222 }
11223
11224 /* Pause for `ms' milliseconds */
11225 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11226 void
11227 TimeDelay(ms)
11228      long ms;
11229 {
11230     TimeMark m1, m2;
11231
11232     GetTimeMark(&m1);
11233     do {
11234         GetTimeMark(&m2);
11235     } while (SubtractTimeMarks(&m2, &m1) < ms);
11236 }
11237
11238 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11239 void
11240 SendMultiLineToICS(buf)
11241      char *buf;
11242 {
11243     char temp[MSG_SIZ+1], *p;
11244     int len;
11245
11246     len = strlen(buf);
11247     if (len > MSG_SIZ)
11248       len = MSG_SIZ;
11249   
11250     strncpy(temp, buf, len);
11251     temp[len] = 0;
11252
11253     p = temp;
11254     while (*p) {
11255         if (*p == '\n' || *p == '\r')
11256           *p = ' ';
11257         ++p;
11258     }
11259
11260     strcat(temp, "\n");
11261     SendToICS(temp);
11262     SendToPlayer(temp, strlen(temp));
11263 }
11264
11265 void
11266 SetWhiteToPlayEvent()
11267 {
11268     if (gameMode == EditPosition) {
11269         blackPlaysFirst = FALSE;
11270         DisplayBothClocks();    /* works because currentMove is 0 */
11271     } else if (gameMode == IcsExamining) {
11272         SendToICS(ics_prefix);
11273         SendToICS("tomove white\n");
11274     }
11275 }
11276
11277 void
11278 SetBlackToPlayEvent()
11279 {
11280     if (gameMode == EditPosition) {
11281         blackPlaysFirst = TRUE;
11282         currentMove = 1;        /* kludge */
11283         DisplayBothClocks();
11284         currentMove = 0;
11285     } else if (gameMode == IcsExamining) {
11286         SendToICS(ics_prefix);
11287         SendToICS("tomove black\n");
11288     }
11289 }
11290
11291 void
11292 EditPositionMenuEvent(selection, x, y)
11293      ChessSquare selection;
11294      int x, y;
11295 {
11296     char buf[MSG_SIZ];
11297     ChessSquare piece = boards[0][y][x];
11298
11299     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11300
11301     switch (selection) {
11302       case ClearBoard:
11303         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11304             SendToICS(ics_prefix);
11305             SendToICS("bsetup clear\n");
11306         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11307             SendToICS(ics_prefix);
11308             SendToICS("clearboard\n");
11309         } else {
11310             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11311                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11312                 for (y = 0; y < BOARD_HEIGHT; y++) {
11313                     if (gameMode == IcsExamining) {
11314                         if (boards[currentMove][y][x] != EmptySquare) {
11315                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11316                                     AAA + x, ONE + y);
11317                             SendToICS(buf);
11318                         }
11319                     } else {
11320                         boards[0][y][x] = p;
11321                     }
11322                 }
11323             }
11324         }
11325         if (gameMode == EditPosition) {
11326             DrawPosition(FALSE, boards[0]);
11327         }
11328         break;
11329
11330       case WhitePlay:
11331         SetWhiteToPlayEvent();
11332         break;
11333
11334       case BlackPlay:
11335         SetBlackToPlayEvent();
11336         break;
11337
11338       case EmptySquare:
11339         if (gameMode == IcsExamining) {
11340             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11341             SendToICS(buf);
11342         } else {
11343             boards[0][y][x] = EmptySquare;
11344             DrawPosition(FALSE, boards[0]);
11345         }
11346         break;
11347
11348       case PromotePiece:
11349         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11350            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11351             selection = (ChessSquare) (PROMOTED piece);
11352         } else if(piece == EmptySquare) selection = WhiteSilver;
11353         else selection = (ChessSquare)((int)piece - 1);
11354         goto defaultlabel;
11355
11356       case DemotePiece:
11357         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11358            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11359             selection = (ChessSquare) (DEMOTED piece);
11360         } else if(piece == EmptySquare) selection = BlackSilver;
11361         else selection = (ChessSquare)((int)piece + 1);       
11362         goto defaultlabel;
11363
11364       case WhiteQueen:
11365       case BlackQueen:
11366         if(gameInfo.variant == VariantShatranj ||
11367            gameInfo.variant == VariantXiangqi  ||
11368            gameInfo.variant == VariantCourier    )
11369             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11370         goto defaultlabel;
11371
11372       case WhiteKing:
11373       case BlackKing:
11374         if(gameInfo.variant == VariantXiangqi)
11375             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11376         if(gameInfo.variant == VariantKnightmate)
11377             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11378       default:
11379         defaultlabel:
11380         if (gameMode == IcsExamining) {
11381             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11382                     PieceToChar(selection), AAA + x, ONE + y);
11383             SendToICS(buf);
11384         } else {
11385             boards[0][y][x] = selection;
11386             DrawPosition(FALSE, boards[0]);
11387         }
11388         break;
11389     }
11390 }
11391
11392
11393 void
11394 DropMenuEvent(selection, x, y)
11395      ChessSquare selection;
11396      int x, y;
11397 {
11398     ChessMove moveType;
11399
11400     switch (gameMode) {
11401       case IcsPlayingWhite:
11402       case MachinePlaysBlack:
11403         if (!WhiteOnMove(currentMove)) {
11404             DisplayMoveError(_("It is Black's turn"));
11405             return;
11406         }
11407         moveType = WhiteDrop;
11408         break;
11409       case IcsPlayingBlack:
11410       case MachinePlaysWhite:
11411         if (WhiteOnMove(currentMove)) {
11412             DisplayMoveError(_("It is White's turn"));
11413             return;
11414         }
11415         moveType = BlackDrop;
11416         break;
11417       case EditGame:
11418         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11419         break;
11420       default:
11421         return;
11422     }
11423
11424     if (moveType == BlackDrop && selection < BlackPawn) {
11425       selection = (ChessSquare) ((int) selection
11426                                  + (int) BlackPawn - (int) WhitePawn);
11427     }
11428     if (boards[currentMove][y][x] != EmptySquare) {
11429         DisplayMoveError(_("That square is occupied"));
11430         return;
11431     }
11432
11433     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11434 }
11435
11436 void
11437 AcceptEvent()
11438 {
11439     /* Accept a pending offer of any kind from opponent */
11440     
11441     if (appData.icsActive) {
11442         SendToICS(ics_prefix);
11443         SendToICS("accept\n");
11444     } else if (cmailMsgLoaded) {
11445         if (currentMove == cmailOldMove &&
11446             commentList[cmailOldMove] != NULL &&
11447             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11448                    "Black offers a draw" : "White offers a draw")) {
11449             TruncateGame();
11450             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11451             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11452         } else {
11453             DisplayError(_("There is no pending offer on this move"), 0);
11454             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11455         }
11456     } else {
11457         /* Not used for offers from chess program */
11458     }
11459 }
11460
11461 void
11462 DeclineEvent()
11463 {
11464     /* Decline a pending offer of any kind from opponent */
11465     
11466     if (appData.icsActive) {
11467         SendToICS(ics_prefix);
11468         SendToICS("decline\n");
11469     } else if (cmailMsgLoaded) {
11470         if (currentMove == cmailOldMove &&
11471             commentList[cmailOldMove] != NULL &&
11472             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11473                    "Black offers a draw" : "White offers a draw")) {
11474 #ifdef NOTDEF
11475             AppendComment(cmailOldMove, "Draw declined");
11476             DisplayComment(cmailOldMove - 1, "Draw declined");
11477 #endif /*NOTDEF*/
11478         } else {
11479             DisplayError(_("There is no pending offer on this move"), 0);
11480         }
11481     } else {
11482         /* Not used for offers from chess program */
11483     }
11484 }
11485
11486 void
11487 RematchEvent()
11488 {
11489     /* Issue ICS rematch command */
11490     if (appData.icsActive) {
11491         SendToICS(ics_prefix);
11492         SendToICS("rematch\n");
11493     }
11494 }
11495
11496 void
11497 CallFlagEvent()
11498 {
11499     /* Call your opponent's flag (claim a win on time) */
11500     if (appData.icsActive) {
11501         SendToICS(ics_prefix);
11502         SendToICS("flag\n");
11503     } else {
11504         switch (gameMode) {
11505           default:
11506             return;
11507           case MachinePlaysWhite:
11508             if (whiteFlag) {
11509                 if (blackFlag)
11510                   GameEnds(GameIsDrawn, "Both players ran out of time",
11511                            GE_PLAYER);
11512                 else
11513                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11514             } else {
11515                 DisplayError(_("Your opponent is not out of time"), 0);
11516             }
11517             break;
11518           case MachinePlaysBlack:
11519             if (blackFlag) {
11520                 if (whiteFlag)
11521                   GameEnds(GameIsDrawn, "Both players ran out of time",
11522                            GE_PLAYER);
11523                 else
11524                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11525             } else {
11526                 DisplayError(_("Your opponent is not out of time"), 0);
11527             }
11528             break;
11529         }
11530     }
11531 }
11532
11533 void
11534 DrawEvent()
11535 {
11536     /* Offer draw or accept pending draw offer from opponent */
11537     
11538     if (appData.icsActive) {
11539         /* Note: tournament rules require draw offers to be
11540            made after you make your move but before you punch
11541            your clock.  Currently ICS doesn't let you do that;
11542            instead, you immediately punch your clock after making
11543            a move, but you can offer a draw at any time. */
11544         
11545         SendToICS(ics_prefix);
11546         SendToICS("draw\n");
11547     } else if (cmailMsgLoaded) {
11548         if (currentMove == cmailOldMove &&
11549             commentList[cmailOldMove] != NULL &&
11550             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11551                    "Black offers a draw" : "White offers a draw")) {
11552             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11553             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11554         } else if (currentMove == cmailOldMove + 1) {
11555             char *offer = WhiteOnMove(cmailOldMove) ?
11556               "White offers a draw" : "Black offers a draw";
11557             AppendComment(currentMove, offer);
11558             DisplayComment(currentMove - 1, offer);
11559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11560         } else {
11561             DisplayError(_("You must make your move before offering a draw"), 0);
11562             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11563         }
11564     } else if (first.offeredDraw) {
11565         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11566     } else {
11567         if (first.sendDrawOffers) {
11568             SendToProgram("draw\n", &first);
11569             userOfferedDraw = TRUE;
11570         }
11571     }
11572 }
11573
11574 void
11575 AdjournEvent()
11576 {
11577     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11578     
11579     if (appData.icsActive) {
11580         SendToICS(ics_prefix);
11581         SendToICS("adjourn\n");
11582     } else {
11583         /* Currently GNU Chess doesn't offer or accept Adjourns */
11584     }
11585 }
11586
11587
11588 void
11589 AbortEvent()
11590 {
11591     /* Offer Abort or accept pending Abort offer from opponent */
11592     
11593     if (appData.icsActive) {
11594         SendToICS(ics_prefix);
11595         SendToICS("abort\n");
11596     } else {
11597         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11598     }
11599 }
11600
11601 void
11602 ResignEvent()
11603 {
11604     /* Resign.  You can do this even if it's not your turn. */
11605     
11606     if (appData.icsActive) {
11607         SendToICS(ics_prefix);
11608         SendToICS("resign\n");
11609     } else {
11610         switch (gameMode) {
11611           case MachinePlaysWhite:
11612             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11613             break;
11614           case MachinePlaysBlack:
11615             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11616             break;
11617           case EditGame:
11618             if (cmailMsgLoaded) {
11619                 TruncateGame();
11620                 if (WhiteOnMove(cmailOldMove)) {
11621                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11622                 } else {
11623                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11624                 }
11625                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11626             }
11627             break;
11628           default:
11629             break;
11630         }
11631     }
11632 }
11633
11634
11635 void
11636 StopObservingEvent()
11637 {
11638     /* Stop observing current games */
11639     SendToICS(ics_prefix);
11640     SendToICS("unobserve\n");
11641 }
11642
11643 void
11644 StopExaminingEvent()
11645 {
11646     /* Stop observing current game */
11647     SendToICS(ics_prefix);
11648     SendToICS("unexamine\n");
11649 }
11650
11651 void
11652 ForwardInner(target)
11653      int target;
11654 {
11655     int limit;
11656
11657     if (appData.debugMode)
11658         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11659                 target, currentMove, forwardMostMove);
11660
11661     if (gameMode == EditPosition)
11662       return;
11663
11664     if (gameMode == PlayFromGameFile && !pausing)
11665       PauseEvent();
11666     
11667     if (gameMode == IcsExamining && pausing)
11668       limit = pauseExamForwardMostMove;
11669     else
11670       limit = forwardMostMove;
11671     
11672     if (target > limit) target = limit;
11673
11674     if (target > 0 && moveList[target - 1][0]) {
11675         int fromX, fromY, toX, toY;
11676         toX = moveList[target - 1][2] - AAA;
11677         toY = moveList[target - 1][3] - ONE;
11678         if (moveList[target - 1][1] == '@') {
11679             if (appData.highlightLastMove) {
11680                 SetHighlights(-1, -1, toX, toY);
11681             }
11682         } else {
11683             fromX = moveList[target - 1][0] - AAA;
11684             fromY = moveList[target - 1][1] - ONE;
11685             if (target == currentMove + 1) {
11686                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11687             }
11688             if (appData.highlightLastMove) {
11689                 SetHighlights(fromX, fromY, toX, toY);
11690             }
11691         }
11692     }
11693     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11694         gameMode == Training || gameMode == PlayFromGameFile || 
11695         gameMode == AnalyzeFile) {
11696         while (currentMove < target) {
11697             SendMoveToProgram(currentMove++, &first);
11698         }
11699     } else {
11700         currentMove = target;
11701     }
11702     
11703     if (gameMode == EditGame || gameMode == EndOfGame) {
11704         whiteTimeRemaining = timeRemaining[0][currentMove];
11705         blackTimeRemaining = timeRemaining[1][currentMove];
11706     }
11707     DisplayBothClocks();
11708     DisplayMove(currentMove - 1);
11709     DrawPosition(FALSE, boards[currentMove]);
11710     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11711     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11712         DisplayComment(currentMove - 1, commentList[currentMove]);
11713     }
11714 }
11715
11716
11717 void
11718 ForwardEvent()
11719 {
11720     if (gameMode == IcsExamining && !pausing) {
11721         SendToICS(ics_prefix);
11722         SendToICS("forward\n");
11723     } else {
11724         ForwardInner(currentMove + 1);
11725     }
11726 }
11727
11728 void
11729 ToEndEvent()
11730 {
11731     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11732         /* to optimze, we temporarily turn off analysis mode while we feed
11733          * the remaining moves to the engine. Otherwise we get analysis output
11734          * after each move.
11735          */ 
11736         if (first.analysisSupport) {
11737           SendToProgram("exit\nforce\n", &first);
11738           first.analyzing = FALSE;
11739         }
11740     }
11741         
11742     if (gameMode == IcsExamining && !pausing) {
11743         SendToICS(ics_prefix);
11744         SendToICS("forward 999999\n");
11745     } else {
11746         ForwardInner(forwardMostMove);
11747     }
11748
11749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11750         /* we have fed all the moves, so reactivate analysis mode */
11751         SendToProgram("analyze\n", &first);
11752         first.analyzing = TRUE;
11753         /*first.maybeThinking = TRUE;*/
11754         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11755     }
11756 }
11757
11758 void
11759 BackwardInner(target)
11760      int target;
11761 {
11762     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11763
11764     if (appData.debugMode)
11765         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11766                 target, currentMove, forwardMostMove);
11767
11768     if (gameMode == EditPosition) return;
11769     if (currentMove <= backwardMostMove) {
11770         ClearHighlights();
11771         DrawPosition(full_redraw, boards[currentMove]);
11772         return;
11773     }
11774     if (gameMode == PlayFromGameFile && !pausing)
11775       PauseEvent();
11776     
11777     if (moveList[target][0]) {
11778         int fromX, fromY, toX, toY;
11779         toX = moveList[target][2] - AAA;
11780         toY = moveList[target][3] - ONE;
11781         if (moveList[target][1] == '@') {
11782             if (appData.highlightLastMove) {
11783                 SetHighlights(-1, -1, toX, toY);
11784             }
11785         } else {
11786             fromX = moveList[target][0] - AAA;
11787             fromY = moveList[target][1] - ONE;
11788             if (target == currentMove - 1) {
11789                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11790             }
11791             if (appData.highlightLastMove) {
11792                 SetHighlights(fromX, fromY, toX, toY);
11793             }
11794         }
11795     }
11796     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11797         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11798         while (currentMove > target) {
11799             SendToProgram("undo\n", &first);
11800             currentMove--;
11801         }
11802     } else {
11803         currentMove = target;
11804     }
11805     
11806     if (gameMode == EditGame || gameMode == EndOfGame) {
11807         whiteTimeRemaining = timeRemaining[0][currentMove];
11808         blackTimeRemaining = timeRemaining[1][currentMove];
11809     }
11810     DisplayBothClocks();
11811     DisplayMove(currentMove - 1);
11812     DrawPosition(full_redraw, boards[currentMove]);
11813     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11814     // [HGM] PV info: routine tests if comment empty
11815     DisplayComment(currentMove - 1, commentList[currentMove]);
11816 }
11817
11818 void
11819 BackwardEvent()
11820 {
11821     if (gameMode == IcsExamining && !pausing) {
11822         SendToICS(ics_prefix);
11823         SendToICS("backward\n");
11824     } else {
11825         BackwardInner(currentMove - 1);
11826     }
11827 }
11828
11829 void
11830 ToStartEvent()
11831 {
11832     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11833         /* to optimze, we temporarily turn off analysis mode while we undo
11834          * all the moves. Otherwise we get analysis output after each undo.
11835          */ 
11836         if (first.analysisSupport) {
11837           SendToProgram("exit\nforce\n", &first);
11838           first.analyzing = FALSE;
11839         }
11840     }
11841
11842     if (gameMode == IcsExamining && !pausing) {
11843         SendToICS(ics_prefix);
11844         SendToICS("backward 999999\n");
11845     } else {
11846         BackwardInner(backwardMostMove);
11847     }
11848
11849     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11850         /* we have fed all the moves, so reactivate analysis mode */
11851         SendToProgram("analyze\n", &first);
11852         first.analyzing = TRUE;
11853         /*first.maybeThinking = TRUE;*/
11854         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11855     }
11856 }
11857
11858 void
11859 ToNrEvent(int to)
11860 {
11861   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11862   if (to >= forwardMostMove) to = forwardMostMove;
11863   if (to <= backwardMostMove) to = backwardMostMove;
11864   if (to < currentMove) {
11865     BackwardInner(to);
11866   } else {
11867     ForwardInner(to);
11868   }
11869 }
11870
11871 void
11872 RevertEvent()
11873 {
11874     if (gameMode != IcsExamining) {
11875         DisplayError(_("You are not examining a game"), 0);
11876         return;
11877     }
11878     if (pausing) {
11879         DisplayError(_("You can't revert while pausing"), 0);
11880         return;
11881     }
11882     SendToICS(ics_prefix);
11883     SendToICS("revert\n");
11884 }
11885
11886 void
11887 RetractMoveEvent()
11888 {
11889     switch (gameMode) {
11890       case MachinePlaysWhite:
11891       case MachinePlaysBlack:
11892         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11893             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11894             return;
11895         }
11896         if (forwardMostMove < 2) return;
11897         currentMove = forwardMostMove = forwardMostMove - 2;
11898         whiteTimeRemaining = timeRemaining[0][currentMove];
11899         blackTimeRemaining = timeRemaining[1][currentMove];
11900         DisplayBothClocks();
11901         DisplayMove(currentMove - 1);
11902         ClearHighlights();/*!! could figure this out*/
11903         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11904         SendToProgram("remove\n", &first);
11905         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11906         break;
11907
11908       case BeginningOfGame:
11909       default:
11910         break;
11911
11912       case IcsPlayingWhite:
11913       case IcsPlayingBlack:
11914         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11915             SendToICS(ics_prefix);
11916             SendToICS("takeback 2\n");
11917         } else {
11918             SendToICS(ics_prefix);
11919             SendToICS("takeback 1\n");
11920         }
11921         break;
11922     }
11923 }
11924
11925 void
11926 MoveNowEvent()
11927 {
11928     ChessProgramState *cps;
11929
11930     switch (gameMode) {
11931       case MachinePlaysWhite:
11932         if (!WhiteOnMove(forwardMostMove)) {
11933             DisplayError(_("It is your turn"), 0);
11934             return;
11935         }
11936         cps = &first;
11937         break;
11938       case MachinePlaysBlack:
11939         if (WhiteOnMove(forwardMostMove)) {
11940             DisplayError(_("It is your turn"), 0);
11941             return;
11942         }
11943         cps = &first;
11944         break;
11945       case TwoMachinesPlay:
11946         if (WhiteOnMove(forwardMostMove) ==
11947             (first.twoMachinesColor[0] == 'w')) {
11948             cps = &first;
11949         } else {
11950             cps = &second;
11951         }
11952         break;
11953       case BeginningOfGame:
11954       default:
11955         return;
11956     }
11957     SendToProgram("?\n", cps);
11958 }
11959
11960 void
11961 TruncateGameEvent()
11962 {
11963     EditGameEvent();
11964     if (gameMode != EditGame) return;
11965     TruncateGame();
11966 }
11967
11968 void
11969 TruncateGame()
11970 {
11971     if (forwardMostMove > currentMove) {
11972         if (gameInfo.resultDetails != NULL) {
11973             free(gameInfo.resultDetails);
11974             gameInfo.resultDetails = NULL;
11975             gameInfo.result = GameUnfinished;
11976         }
11977         forwardMostMove = currentMove;
11978         HistorySet(parseList, backwardMostMove, forwardMostMove,
11979                    currentMove-1);
11980     }
11981 }
11982
11983 void
11984 HintEvent()
11985 {
11986     if (appData.noChessProgram) return;
11987     switch (gameMode) {
11988       case MachinePlaysWhite:
11989         if (WhiteOnMove(forwardMostMove)) {
11990             DisplayError(_("Wait until your turn"), 0);
11991             return;
11992         }
11993         break;
11994       case BeginningOfGame:
11995       case MachinePlaysBlack:
11996         if (!WhiteOnMove(forwardMostMove)) {
11997             DisplayError(_("Wait until your turn"), 0);
11998             return;
11999         }
12000         break;
12001       default:
12002         DisplayError(_("No hint available"), 0);
12003         return;
12004     }
12005     SendToProgram("hint\n", &first);
12006     hintRequested = TRUE;
12007 }
12008
12009 void
12010 BookEvent()
12011 {
12012     if (appData.noChessProgram) return;
12013     switch (gameMode) {
12014       case MachinePlaysWhite:
12015         if (WhiteOnMove(forwardMostMove)) {
12016             DisplayError(_("Wait until your turn"), 0);
12017             return;
12018         }
12019         break;
12020       case BeginningOfGame:
12021       case MachinePlaysBlack:
12022         if (!WhiteOnMove(forwardMostMove)) {
12023             DisplayError(_("Wait until your turn"), 0);
12024             return;
12025         }
12026         break;
12027       case EditPosition:
12028         EditPositionDone();
12029         break;
12030       case TwoMachinesPlay:
12031         return;
12032       default:
12033         break;
12034     }
12035     SendToProgram("bk\n", &first);
12036     bookOutput[0] = NULLCHAR;
12037     bookRequested = TRUE;
12038 }
12039
12040 void
12041 AboutGameEvent()
12042 {
12043     char *tags = PGNTags(&gameInfo);
12044     TagsPopUp(tags, CmailMsg());
12045     free(tags);
12046 }
12047
12048 /* end button procedures */
12049
12050 void
12051 PrintPosition(fp, move)
12052      FILE *fp;
12053      int move;
12054 {
12055     int i, j;
12056     
12057     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12058         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12059             char c = PieceToChar(boards[move][i][j]);
12060             fputc(c == 'x' ? '.' : c, fp);
12061             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12062         }
12063     }
12064     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12065       fprintf(fp, "white to play\n");
12066     else
12067       fprintf(fp, "black to play\n");
12068 }
12069
12070 void
12071 PrintOpponents(fp)
12072      FILE *fp;
12073 {
12074     if (gameInfo.white != NULL) {
12075         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12076     } else {
12077         fprintf(fp, "\n");
12078     }
12079 }
12080
12081 /* Find last component of program's own name, using some heuristics */
12082 void
12083 TidyProgramName(prog, host, buf)
12084      char *prog, *host, buf[MSG_SIZ];
12085 {
12086     char *p, *q;
12087     int local = (strcmp(host, "localhost") == 0);
12088     while (!local && (p = strchr(prog, ';')) != NULL) {
12089         p++;
12090         while (*p == ' ') p++;
12091         prog = p;
12092     }
12093     if (*prog == '"' || *prog == '\'') {
12094         q = strchr(prog + 1, *prog);
12095     } else {
12096         q = strchr(prog, ' ');
12097     }
12098     if (q == NULL) q = prog + strlen(prog);
12099     p = q;
12100     while (p >= prog && *p != '/' && *p != '\\') p--;
12101     p++;
12102     if(p == prog && *p == '"') p++;
12103     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12104     memcpy(buf, p, q - p);
12105     buf[q - p] = NULLCHAR;
12106     if (!local) {
12107         strcat(buf, "@");
12108         strcat(buf, host);
12109     }
12110 }
12111
12112 char *
12113 TimeControlTagValue()
12114 {
12115     char buf[MSG_SIZ];
12116     if (!appData.clockMode) {
12117         strcpy(buf, "-");
12118     } else if (movesPerSession > 0) {
12119         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12120     } else if (timeIncrement == 0) {
12121         sprintf(buf, "%ld", timeControl/1000);
12122     } else {
12123         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12124     }
12125     return StrSave(buf);
12126 }
12127
12128 void
12129 SetGameInfo()
12130 {
12131     /* This routine is used only for certain modes */
12132     VariantClass v = gameInfo.variant;
12133     ClearGameInfo(&gameInfo);
12134     gameInfo.variant = v;
12135
12136     switch (gameMode) {
12137       case MachinePlaysWhite:
12138         gameInfo.event = StrSave( appData.pgnEventHeader );
12139         gameInfo.site = StrSave(HostName());
12140         gameInfo.date = PGNDate();
12141         gameInfo.round = StrSave("-");
12142         gameInfo.white = StrSave(first.tidy);
12143         gameInfo.black = StrSave(UserName());
12144         gameInfo.timeControl = TimeControlTagValue();
12145         break;
12146
12147       case MachinePlaysBlack:
12148         gameInfo.event = StrSave( appData.pgnEventHeader );
12149         gameInfo.site = StrSave(HostName());
12150         gameInfo.date = PGNDate();
12151         gameInfo.round = StrSave("-");
12152         gameInfo.white = StrSave(UserName());
12153         gameInfo.black = StrSave(first.tidy);
12154         gameInfo.timeControl = TimeControlTagValue();
12155         break;
12156
12157       case TwoMachinesPlay:
12158         gameInfo.event = StrSave( appData.pgnEventHeader );
12159         gameInfo.site = StrSave(HostName());
12160         gameInfo.date = PGNDate();
12161         if (matchGame > 0) {
12162             char buf[MSG_SIZ];
12163             sprintf(buf, "%d", matchGame);
12164             gameInfo.round = StrSave(buf);
12165         } else {
12166             gameInfo.round = StrSave("-");
12167         }
12168         if (first.twoMachinesColor[0] == 'w') {
12169             gameInfo.white = StrSave(first.tidy);
12170             gameInfo.black = StrSave(second.tidy);
12171         } else {
12172             gameInfo.white = StrSave(second.tidy);
12173             gameInfo.black = StrSave(first.tidy);
12174         }
12175         gameInfo.timeControl = TimeControlTagValue();
12176         break;
12177
12178       case EditGame:
12179         gameInfo.event = StrSave("Edited game");
12180         gameInfo.site = StrSave(HostName());
12181         gameInfo.date = PGNDate();
12182         gameInfo.round = StrSave("-");
12183         gameInfo.white = StrSave("-");
12184         gameInfo.black = StrSave("-");
12185         break;
12186
12187       case EditPosition:
12188         gameInfo.event = StrSave("Edited position");
12189         gameInfo.site = StrSave(HostName());
12190         gameInfo.date = PGNDate();
12191         gameInfo.round = StrSave("-");
12192         gameInfo.white = StrSave("-");
12193         gameInfo.black = StrSave("-");
12194         break;
12195
12196       case IcsPlayingWhite:
12197       case IcsPlayingBlack:
12198       case IcsObserving:
12199       case IcsExamining:
12200         break;
12201
12202       case PlayFromGameFile:
12203         gameInfo.event = StrSave("Game from non-PGN file");
12204         gameInfo.site = StrSave(HostName());
12205         gameInfo.date = PGNDate();
12206         gameInfo.round = StrSave("-");
12207         gameInfo.white = StrSave("?");
12208         gameInfo.black = StrSave("?");
12209         break;
12210
12211       default:
12212         break;
12213     }
12214 }
12215
12216 void
12217 ReplaceComment(index, text)
12218      int index;
12219      char *text;
12220 {
12221     int len;
12222
12223     while (*text == '\n') text++;
12224     len = strlen(text);
12225     while (len > 0 && text[len - 1] == '\n') len--;
12226
12227     if (commentList[index] != NULL)
12228       free(commentList[index]);
12229
12230     if (len == 0) {
12231         commentList[index] = NULL;
12232         return;
12233     }
12234     commentList[index] = (char *) malloc(len + 2);
12235     strncpy(commentList[index], text, len);
12236     commentList[index][len] = '\n';
12237     commentList[index][len + 1] = NULLCHAR;
12238 }
12239
12240 void
12241 CrushCRs(text)
12242      char *text;
12243 {
12244   char *p = text;
12245   char *q = text;
12246   char ch;
12247
12248   do {
12249     ch = *p++;
12250     if (ch == '\r') continue;
12251     *q++ = ch;
12252   } while (ch != '\0');
12253 }
12254
12255 void
12256 AppendComment(index, text)
12257      int index;
12258      char *text;
12259 {
12260     int oldlen, len;
12261     char *old;
12262
12263     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12264
12265     CrushCRs(text);
12266     while (*text == '\n') text++;
12267     len = strlen(text);
12268     while (len > 0 && text[len - 1] == '\n') len--;
12269
12270     if (len == 0) return;
12271
12272     if (commentList[index] != NULL) {
12273         old = commentList[index];
12274         oldlen = strlen(old);
12275         commentList[index] = (char *) malloc(oldlen + len + 2);
12276         strcpy(commentList[index], old);
12277         free(old);
12278         strncpy(&commentList[index][oldlen], text, len);
12279         commentList[index][oldlen + len] = '\n';
12280         commentList[index][oldlen + len + 1] = NULLCHAR;
12281     } else {
12282         commentList[index] = (char *) malloc(len + 2);
12283         strncpy(commentList[index], text, len);
12284         commentList[index][len] = '\n';
12285         commentList[index][len + 1] = NULLCHAR;
12286     }
12287 }
12288
12289 static char * FindStr( char * text, char * sub_text )
12290 {
12291     char * result = strstr( text, sub_text );
12292
12293     if( result != NULL ) {
12294         result += strlen( sub_text );
12295     }
12296
12297     return result;
12298 }
12299
12300 /* [AS] Try to extract PV info from PGN comment */
12301 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12302 char *GetInfoFromComment( int index, char * text )
12303 {
12304     char * sep = text;
12305
12306     if( text != NULL && index > 0 ) {
12307         int score = 0;
12308         int depth = 0;
12309         int time = -1, sec = 0, deci;
12310         char * s_eval = FindStr( text, "[%eval " );
12311         char * s_emt = FindStr( text, "[%emt " );
12312
12313         if( s_eval != NULL || s_emt != NULL ) {
12314             /* New style */
12315             char delim;
12316
12317             if( s_eval != NULL ) {
12318                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12319                     return text;
12320                 }
12321
12322                 if( delim != ']' ) {
12323                     return text;
12324                 }
12325             }
12326
12327             if( s_emt != NULL ) {
12328             }
12329         }
12330         else {
12331             /* We expect something like: [+|-]nnn.nn/dd */
12332             int score_lo = 0;
12333
12334             sep = strchr( text, '/' );
12335             if( sep == NULL || sep < (text+4) ) {
12336                 return text;
12337             }
12338
12339             time = -1; sec = -1; deci = -1;
12340             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12341                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12342                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12343                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12344                 return text;
12345             }
12346
12347             if( score_lo < 0 || score_lo >= 100 ) {
12348                 return text;
12349             }
12350
12351             if(sec >= 0) time = 600*time + 10*sec; else
12352             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12353
12354             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12355
12356             /* [HGM] PV time: now locate end of PV info */
12357             while( *++sep >= '0' && *sep <= '9'); // strip depth
12358             if(time >= 0)
12359             while( *++sep >= '0' && *sep <= '9'); // strip time
12360             if(sec >= 0)
12361             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12362             if(deci >= 0)
12363             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12364             while(*sep == ' ') sep++;
12365         }
12366
12367         if( depth <= 0 ) {
12368             return text;
12369         }
12370
12371         if( time < 0 ) {
12372             time = -1;
12373         }
12374
12375         pvInfoList[index-1].depth = depth;
12376         pvInfoList[index-1].score = score;
12377         pvInfoList[index-1].time  = 10*time; // centi-sec
12378     }
12379     return sep;
12380 }
12381
12382 void
12383 SendToProgram(message, cps)
12384      char *message;
12385      ChessProgramState *cps;
12386 {
12387     int count, outCount, error;
12388     char buf[MSG_SIZ];
12389
12390     if (cps->pr == NULL) return;
12391     Attention(cps);
12392     
12393     if (appData.debugMode) {
12394         TimeMark now;
12395         GetTimeMark(&now);
12396         fprintf(debugFP, "%ld >%-6s: %s", 
12397                 SubtractTimeMarks(&now, &programStartTime),
12398                 cps->which, message);
12399     }
12400     
12401     count = strlen(message);
12402     outCount = OutputToProcess(cps->pr, message, count, &error);
12403     if (outCount < count && !exiting 
12404                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12405         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12406         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12407             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12408                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12409                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12410             } else {
12411                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12412             }
12413             gameInfo.resultDetails = buf;
12414         }
12415         DisplayFatalError(buf, error, 1);
12416     }
12417 }
12418
12419 void
12420 ReceiveFromProgram(isr, closure, message, count, error)
12421      InputSourceRef isr;
12422      VOIDSTAR closure;
12423      char *message;
12424      int count;
12425      int error;
12426 {
12427     char *end_str;
12428     char buf[MSG_SIZ];
12429     ChessProgramState *cps = (ChessProgramState *)closure;
12430
12431     if (isr != cps->isr) return; /* Killed intentionally */
12432     if (count <= 0) {
12433         if (count == 0) {
12434             sprintf(buf,
12435                     _("Error: %s chess program (%s) exited unexpectedly"),
12436                     cps->which, cps->program);
12437         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12438                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12439                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12440                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12441                 } else {
12442                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12443                 }
12444                 gameInfo.resultDetails = buf;
12445             }
12446             RemoveInputSource(cps->isr);
12447             DisplayFatalError(buf, 0, 1);
12448         } else {
12449             sprintf(buf,
12450                     _("Error reading from %s chess program (%s)"),
12451                     cps->which, cps->program);
12452             RemoveInputSource(cps->isr);
12453
12454             /* [AS] Program is misbehaving badly... kill it */
12455             if( count == -2 ) {
12456                 DestroyChildProcess( cps->pr, 9 );
12457                 cps->pr = NoProc;
12458             }
12459
12460             DisplayFatalError(buf, error, 1);
12461         }
12462         return;
12463     }
12464     
12465     if ((end_str = strchr(message, '\r')) != NULL)
12466       *end_str = NULLCHAR;
12467     if ((end_str = strchr(message, '\n')) != NULL)
12468       *end_str = NULLCHAR;
12469     
12470     if (appData.debugMode) {
12471         TimeMark now; int print = 1;
12472         char *quote = ""; char c; int i;
12473
12474         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12475                 char start = message[0];
12476                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12477                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12478                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12479                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12480                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12481                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12482                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12483                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12484                         { quote = "# "; print = (appData.engineComments == 2); }
12485                 message[0] = start; // restore original message
12486         }
12487         if(print) {
12488                 GetTimeMark(&now);
12489                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12490                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12491                         quote,
12492                         message);
12493         }
12494     }
12495
12496     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12497     if (appData.icsEngineAnalyze) {
12498         if (strstr(message, "whisper") != NULL ||
12499              strstr(message, "kibitz") != NULL || 
12500             strstr(message, "tellics") != NULL) return;
12501     }
12502
12503     HandleMachineMove(message, cps);
12504 }
12505
12506
12507 void
12508 SendTimeControl(cps, mps, tc, inc, sd, st)
12509      ChessProgramState *cps;
12510      int mps, inc, sd, st;
12511      long tc;
12512 {
12513     char buf[MSG_SIZ];
12514     int seconds;
12515
12516     if( timeControl_2 > 0 ) {
12517         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12518             tc = timeControl_2;
12519         }
12520     }
12521     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12522     inc /= cps->timeOdds;
12523     st  /= cps->timeOdds;
12524
12525     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12526
12527     if (st > 0) {
12528       /* Set exact time per move, normally using st command */
12529       if (cps->stKludge) {
12530         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12531         seconds = st % 60;
12532         if (seconds == 0) {
12533           sprintf(buf, "level 1 %d\n", st/60);
12534         } else {
12535           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12536         }
12537       } else {
12538         sprintf(buf, "st %d\n", st);
12539       }
12540     } else {
12541       /* Set conventional or incremental time control, using level command */
12542       if (seconds == 0) {
12543         /* Note old gnuchess bug -- minutes:seconds used to not work.
12544            Fixed in later versions, but still avoid :seconds
12545            when seconds is 0. */
12546         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12547       } else {
12548         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12549                 seconds, inc/1000);
12550       }
12551     }
12552     SendToProgram(buf, cps);
12553
12554     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12555     /* Orthogonally, limit search to given depth */
12556     if (sd > 0) {
12557       if (cps->sdKludge) {
12558         sprintf(buf, "depth\n%d\n", sd);
12559       } else {
12560         sprintf(buf, "sd %d\n", sd);
12561       }
12562       SendToProgram(buf, cps);
12563     }
12564
12565     if(cps->nps > 0) { /* [HGM] nps */
12566         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12567         else {
12568                 sprintf(buf, "nps %d\n", cps->nps);
12569               SendToProgram(buf, cps);
12570         }
12571     }
12572 }
12573
12574 ChessProgramState *WhitePlayer()
12575 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12576 {
12577     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12578        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12579         return &second;
12580     return &first;
12581 }
12582
12583 void
12584 SendTimeRemaining(cps, machineWhite)
12585      ChessProgramState *cps;
12586      int /*boolean*/ machineWhite;
12587 {
12588     char message[MSG_SIZ];
12589     long time, otime;
12590
12591     /* Note: this routine must be called when the clocks are stopped
12592        or when they have *just* been set or switched; otherwise
12593        it will be off by the time since the current tick started.
12594     */
12595     if (machineWhite) {
12596         time = whiteTimeRemaining / 10;
12597         otime = blackTimeRemaining / 10;
12598     } else {
12599         time = blackTimeRemaining / 10;
12600         otime = whiteTimeRemaining / 10;
12601     }
12602     /* [HGM] translate opponent's time by time-odds factor */
12603     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12604     if (appData.debugMode) {
12605         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12606     }
12607
12608     if (time <= 0) time = 1;
12609     if (otime <= 0) otime = 1;
12610     
12611     sprintf(message, "time %ld\n", time);
12612     SendToProgram(message, cps);
12613
12614     sprintf(message, "otim %ld\n", otime);
12615     SendToProgram(message, cps);
12616 }
12617
12618 int
12619 BoolFeature(p, name, loc, cps)
12620      char **p;
12621      char *name;
12622      int *loc;
12623      ChessProgramState *cps;
12624 {
12625   char buf[MSG_SIZ];
12626   int len = strlen(name);
12627   int val;
12628   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12629     (*p) += len + 1;
12630     sscanf(*p, "%d", &val);
12631     *loc = (val != 0);
12632     while (**p && **p != ' ') (*p)++;
12633     sprintf(buf, "accepted %s\n", name);
12634     SendToProgram(buf, cps);
12635     return TRUE;
12636   }
12637   return FALSE;
12638 }
12639
12640 int
12641 IntFeature(p, name, loc, cps)
12642      char **p;
12643      char *name;
12644      int *loc;
12645      ChessProgramState *cps;
12646 {
12647   char buf[MSG_SIZ];
12648   int len = strlen(name);
12649   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12650     (*p) += len + 1;
12651     sscanf(*p, "%d", loc);
12652     while (**p && **p != ' ') (*p)++;
12653     sprintf(buf, "accepted %s\n", name);
12654     SendToProgram(buf, cps);
12655     return TRUE;
12656   }
12657   return FALSE;
12658 }
12659
12660 int
12661 StringFeature(p, name, loc, cps)
12662      char **p;
12663      char *name;
12664      char loc[];
12665      ChessProgramState *cps;
12666 {
12667   char buf[MSG_SIZ];
12668   int len = strlen(name);
12669   if (strncmp((*p), name, len) == 0
12670       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12671     (*p) += len + 2;
12672     sscanf(*p, "%[^\"]", loc);
12673     while (**p && **p != '\"') (*p)++;
12674     if (**p == '\"') (*p)++;
12675     sprintf(buf, "accepted %s\n", name);
12676     SendToProgram(buf, cps);
12677     return TRUE;
12678   }
12679   return FALSE;
12680 }
12681
12682 int 
12683 ParseOption(Option *opt, ChessProgramState *cps)
12684 // [HGM] options: process the string that defines an engine option, and determine
12685 // name, type, default value, and allowed value range
12686 {
12687         char *p, *q, buf[MSG_SIZ];
12688         int n, min = (-1)<<31, max = 1<<31, def;
12689
12690         if(p = strstr(opt->name, " -spin ")) {
12691             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12692             if(max < min) max = min; // enforce consistency
12693             if(def < min) def = min;
12694             if(def > max) def = max;
12695             opt->value = def;
12696             opt->min = min;
12697             opt->max = max;
12698             opt->type = Spin;
12699         } else if((p = strstr(opt->name, " -slider "))) {
12700             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12701             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12702             if(max < min) max = min; // enforce consistency
12703             if(def < min) def = min;
12704             if(def > max) def = max;
12705             opt->value = def;
12706             opt->min = min;
12707             opt->max = max;
12708             opt->type = Spin; // Slider;
12709         } else if((p = strstr(opt->name, " -string "))) {
12710             opt->textValue = p+9;
12711             opt->type = TextBox;
12712         } else if((p = strstr(opt->name, " -file "))) {
12713             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12714             opt->textValue = p+7;
12715             opt->type = TextBox; // FileName;
12716         } else if((p = strstr(opt->name, " -path "))) {
12717             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12718             opt->textValue = p+7;
12719             opt->type = TextBox; // PathName;
12720         } else if(p = strstr(opt->name, " -check ")) {
12721             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12722             opt->value = (def != 0);
12723             opt->type = CheckBox;
12724         } else if(p = strstr(opt->name, " -combo ")) {
12725             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12726             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12727             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12728             opt->value = n = 0;
12729             while(q = StrStr(q, " /// ")) {
12730                 n++; *q = 0;    // count choices, and null-terminate each of them
12731                 q += 5;
12732                 if(*q == '*') { // remember default, which is marked with * prefix
12733                     q++;
12734                     opt->value = n;
12735                 }
12736                 cps->comboList[cps->comboCnt++] = q;
12737             }
12738             cps->comboList[cps->comboCnt++] = NULL;
12739             opt->max = n + 1;
12740             opt->type = ComboBox;
12741         } else if(p = strstr(opt->name, " -button")) {
12742             opt->type = Button;
12743         } else if(p = strstr(opt->name, " -save")) {
12744             opt->type = SaveButton;
12745         } else return FALSE;
12746         *p = 0; // terminate option name
12747         // now look if the command-line options define a setting for this engine option.
12748         if(cps->optionSettings && cps->optionSettings[0])
12749             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12750         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12751                 sprintf(buf, "option %s", p);
12752                 if(p = strstr(buf, ",")) *p = 0;
12753                 strcat(buf, "\n");
12754                 SendToProgram(buf, cps);
12755         }
12756         return TRUE;
12757 }
12758
12759 void
12760 FeatureDone(cps, val)
12761      ChessProgramState* cps;
12762      int val;
12763 {
12764   DelayedEventCallback cb = GetDelayedEvent();
12765   if ((cb == InitBackEnd3 && cps == &first) ||
12766       (cb == TwoMachinesEventIfReady && cps == &second)) {
12767     CancelDelayedEvent();
12768     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12769   }
12770   cps->initDone = val;
12771 }
12772
12773 /* Parse feature command from engine */
12774 void
12775 ParseFeatures(args, cps)
12776      char* args;
12777      ChessProgramState *cps;  
12778 {
12779   char *p = args;
12780   char *q;
12781   int val;
12782   char buf[MSG_SIZ];
12783
12784   for (;;) {
12785     while (*p == ' ') p++;
12786     if (*p == NULLCHAR) return;
12787
12788     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12789     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12790     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12791     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12792     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12793     if (BoolFeature(&p, "reuse", &val, cps)) {
12794       /* Engine can disable reuse, but can't enable it if user said no */
12795       if (!val) cps->reuse = FALSE;
12796       continue;
12797     }
12798     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12799     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12800       if (gameMode == TwoMachinesPlay) {
12801         DisplayTwoMachinesTitle();
12802       } else {
12803         DisplayTitle("");
12804       }
12805       continue;
12806     }
12807     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12808     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12809     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12810     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12811     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12812     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12813     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12814     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12815     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12816     if (IntFeature(&p, "done", &val, cps)) {
12817       FeatureDone(cps, val);
12818       continue;
12819     }
12820     /* Added by Tord: */
12821     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12822     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12823     /* End of additions by Tord */
12824
12825     /* [HGM] added features: */
12826     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12827     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12828     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12829     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12830     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12831     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12832     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12833         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12834             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12835             SendToProgram(buf, cps);
12836             continue;
12837         }
12838         if(cps->nrOptions >= MAX_OPTIONS) {
12839             cps->nrOptions--;
12840             sprintf(buf, "%s engine has too many options\n", cps->which);
12841             DisplayError(buf, 0);
12842         }
12843         continue;
12844     }
12845     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12846     /* End of additions by HGM */
12847
12848     /* unknown feature: complain and skip */
12849     q = p;
12850     while (*q && *q != '=') q++;
12851     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12852     SendToProgram(buf, cps);
12853     p = q;
12854     if (*p == '=') {
12855       p++;
12856       if (*p == '\"') {
12857         p++;
12858         while (*p && *p != '\"') p++;
12859         if (*p == '\"') p++;
12860       } else {
12861         while (*p && *p != ' ') p++;
12862       }
12863     }
12864   }
12865
12866 }
12867
12868 void
12869 PeriodicUpdatesEvent(newState)
12870      int newState;
12871 {
12872     if (newState == appData.periodicUpdates)
12873       return;
12874
12875     appData.periodicUpdates=newState;
12876
12877     /* Display type changes, so update it now */
12878 //    DisplayAnalysis();
12879
12880     /* Get the ball rolling again... */
12881     if (newState) {
12882         AnalysisPeriodicEvent(1);
12883         StartAnalysisClock();
12884     }
12885 }
12886
12887 void
12888 PonderNextMoveEvent(newState)
12889      int newState;
12890 {
12891     if (newState == appData.ponderNextMove) return;
12892     if (gameMode == EditPosition) EditPositionDone();
12893     if (newState) {
12894         SendToProgram("hard\n", &first);
12895         if (gameMode == TwoMachinesPlay) {
12896             SendToProgram("hard\n", &second);
12897         }
12898     } else {
12899         SendToProgram("easy\n", &first);
12900         thinkOutput[0] = NULLCHAR;
12901         if (gameMode == TwoMachinesPlay) {
12902             SendToProgram("easy\n", &second);
12903         }
12904     }
12905     appData.ponderNextMove = newState;
12906 }
12907
12908 void
12909 NewSettingEvent(option, command, value)
12910      char *command;
12911      int option, value;
12912 {
12913     char buf[MSG_SIZ];
12914
12915     if (gameMode == EditPosition) EditPositionDone();
12916     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12917     SendToProgram(buf, &first);
12918     if (gameMode == TwoMachinesPlay) {
12919         SendToProgram(buf, &second);
12920     }
12921 }
12922
12923 void
12924 ShowThinkingEvent()
12925 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12926 {
12927     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12928     int newState = appData.showThinking
12929         // [HGM] thinking: other features now need thinking output as well
12930         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12931     
12932     if (oldState == newState) return;
12933     oldState = newState;
12934     if (gameMode == EditPosition) EditPositionDone();
12935     if (oldState) {
12936         SendToProgram("post\n", &first);
12937         if (gameMode == TwoMachinesPlay) {
12938             SendToProgram("post\n", &second);
12939         }
12940     } else {
12941         SendToProgram("nopost\n", &first);
12942         thinkOutput[0] = NULLCHAR;
12943         if (gameMode == TwoMachinesPlay) {
12944             SendToProgram("nopost\n", &second);
12945         }
12946     }
12947 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12948 }
12949
12950 void
12951 AskQuestionEvent(title, question, replyPrefix, which)
12952      char *title; char *question; char *replyPrefix; char *which;
12953 {
12954   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12955   if (pr == NoProc) return;
12956   AskQuestion(title, question, replyPrefix, pr);
12957 }
12958
12959 void
12960 DisplayMove(moveNumber)
12961      int moveNumber;
12962 {
12963     char message[MSG_SIZ];
12964     char res[MSG_SIZ];
12965     char cpThinkOutput[MSG_SIZ];
12966
12967     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12968     
12969     if (moveNumber == forwardMostMove - 1 || 
12970         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12971
12972         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12973
12974         if (strchr(cpThinkOutput, '\n')) {
12975             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12976         }
12977     } else {
12978         *cpThinkOutput = NULLCHAR;
12979     }
12980
12981     /* [AS] Hide thinking from human user */
12982     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12983         *cpThinkOutput = NULLCHAR;
12984         if( thinkOutput[0] != NULLCHAR ) {
12985             int i;
12986
12987             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12988                 cpThinkOutput[i] = '.';
12989             }
12990             cpThinkOutput[i] = NULLCHAR;
12991             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12992         }
12993     }
12994
12995     if (moveNumber == forwardMostMove - 1 &&
12996         gameInfo.resultDetails != NULL) {
12997         if (gameInfo.resultDetails[0] == NULLCHAR) {
12998             sprintf(res, " %s", PGNResult(gameInfo.result));
12999         } else {
13000             sprintf(res, " {%s} %s",
13001                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13002         }
13003     } else {
13004         res[0] = NULLCHAR;
13005     }
13006
13007     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13008         DisplayMessage(res, cpThinkOutput);
13009     } else {
13010         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13011                 WhiteOnMove(moveNumber) ? " " : ".. ",
13012                 parseList[moveNumber], res);
13013         DisplayMessage(message, cpThinkOutput);
13014     }
13015 }
13016
13017 void
13018 DisplayComment(moveNumber, text)
13019      int moveNumber;
13020      char *text;
13021 {
13022     char title[MSG_SIZ];
13023     char buf[8000]; // comment can be long!
13024     int score, depth;
13025
13026     if( appData.autoDisplayComment ) {
13027         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13028             strcpy(title, "Comment");
13029         } else {
13030             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13031                     WhiteOnMove(moveNumber) ? " " : ".. ",
13032                     parseList[moveNumber]);
13033         }
13034         // [HGM] PV info: display PV info together with (or as) comment
13035         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13036             if(text == NULL) text = "";                                           
13037             score = pvInfoList[moveNumber].score;
13038             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13039                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13040             text = buf;
13041         }
13042     } else title[0] = 0;
13043
13044     if (text != NULL)
13045         CommentPopUp(title, text);
13046 }
13047
13048 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13049  * might be busy thinking or pondering.  It can be omitted if your
13050  * gnuchess is configured to stop thinking immediately on any user
13051  * input.  However, that gnuchess feature depends on the FIONREAD
13052  * ioctl, which does not work properly on some flavors of Unix.
13053  */
13054 void
13055 Attention(cps)
13056      ChessProgramState *cps;
13057 {
13058 #if ATTENTION
13059     if (!cps->useSigint) return;
13060     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13061     switch (gameMode) {
13062       case MachinePlaysWhite:
13063       case MachinePlaysBlack:
13064       case TwoMachinesPlay:
13065       case IcsPlayingWhite:
13066       case IcsPlayingBlack:
13067       case AnalyzeMode:
13068       case AnalyzeFile:
13069         /* Skip if we know it isn't thinking */
13070         if (!cps->maybeThinking) return;
13071         if (appData.debugMode)
13072           fprintf(debugFP, "Interrupting %s\n", cps->which);
13073         InterruptChildProcess(cps->pr);
13074         cps->maybeThinking = FALSE;
13075         break;
13076       default:
13077         break;
13078     }
13079 #endif /*ATTENTION*/
13080 }
13081
13082 int
13083 CheckFlags()
13084 {
13085     if (whiteTimeRemaining <= 0) {
13086         if (!whiteFlag) {
13087             whiteFlag = TRUE;
13088             if (appData.icsActive) {
13089                 if (appData.autoCallFlag &&
13090                     gameMode == IcsPlayingBlack && !blackFlag) {
13091                   SendToICS(ics_prefix);
13092                   SendToICS("flag\n");
13093                 }
13094             } else {
13095                 if (blackFlag) {
13096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13097                 } else {
13098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13099                     if (appData.autoCallFlag) {
13100                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13101                         return TRUE;
13102                     }
13103                 }
13104             }
13105         }
13106     }
13107     if (blackTimeRemaining <= 0) {
13108         if (!blackFlag) {
13109             blackFlag = TRUE;
13110             if (appData.icsActive) {
13111                 if (appData.autoCallFlag &&
13112                     gameMode == IcsPlayingWhite && !whiteFlag) {
13113                   SendToICS(ics_prefix);
13114                   SendToICS("flag\n");
13115                 }
13116             } else {
13117                 if (whiteFlag) {
13118                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13119                 } else {
13120                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13121                     if (appData.autoCallFlag) {
13122                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13123                         return TRUE;
13124                     }
13125                 }
13126             }
13127         }
13128     }
13129     return FALSE;
13130 }
13131
13132 void
13133 CheckTimeControl()
13134 {
13135     if (!appData.clockMode || appData.icsActive ||
13136         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13137
13138     /*
13139      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13140      */
13141     if ( !WhiteOnMove(forwardMostMove) )
13142         /* White made time control */
13143         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13144         /* [HGM] time odds: correct new time quota for time odds! */
13145                                             / WhitePlayer()->timeOdds;
13146       else
13147         /* Black made time control */
13148         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13149                                             / WhitePlayer()->other->timeOdds;
13150 }
13151
13152 void
13153 DisplayBothClocks()
13154 {
13155     int wom = gameMode == EditPosition ?
13156       !blackPlaysFirst : WhiteOnMove(currentMove);
13157     DisplayWhiteClock(whiteTimeRemaining, wom);
13158     DisplayBlackClock(blackTimeRemaining, !wom);
13159 }
13160
13161
13162 /* Timekeeping seems to be a portability nightmare.  I think everyone
13163    has ftime(), but I'm really not sure, so I'm including some ifdefs
13164    to use other calls if you don't.  Clocks will be less accurate if
13165    you have neither ftime nor gettimeofday.
13166 */
13167
13168 /* VS 2008 requires the #include outside of the function */
13169 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13170 #include <sys/timeb.h>
13171 #endif
13172
13173 /* Get the current time as a TimeMark */
13174 void
13175 GetTimeMark(tm)
13176      TimeMark *tm;
13177 {
13178 #if HAVE_GETTIMEOFDAY
13179
13180     struct timeval timeVal;
13181     struct timezone timeZone;
13182
13183     gettimeofday(&timeVal, &timeZone);
13184     tm->sec = (long) timeVal.tv_sec; 
13185     tm->ms = (int) (timeVal.tv_usec / 1000L);
13186
13187 #else /*!HAVE_GETTIMEOFDAY*/
13188 #if HAVE_FTIME
13189
13190 // include <sys/timeb.h> / moved to just above start of function
13191     struct timeb timeB;
13192
13193     ftime(&timeB);
13194     tm->sec = (long) timeB.time;
13195     tm->ms = (int) timeB.millitm;
13196
13197 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13198     tm->sec = (long) time(NULL);
13199     tm->ms = 0;
13200 #endif
13201 #endif
13202 }
13203
13204 /* Return the difference in milliseconds between two
13205    time marks.  We assume the difference will fit in a long!
13206 */
13207 long
13208 SubtractTimeMarks(tm2, tm1)
13209      TimeMark *tm2, *tm1;
13210 {
13211     return 1000L*(tm2->sec - tm1->sec) +
13212            (long) (tm2->ms - tm1->ms);
13213 }
13214
13215
13216 /*
13217  * Code to manage the game clocks.
13218  *
13219  * In tournament play, black starts the clock and then white makes a move.
13220  * We give the human user a slight advantage if he is playing white---the
13221  * clocks don't run until he makes his first move, so it takes zero time.
13222  * Also, we don't account for network lag, so we could get out of sync
13223  * with GNU Chess's clock -- but then, referees are always right.  
13224  */
13225
13226 static TimeMark tickStartTM;
13227 static long intendedTickLength;
13228
13229 long
13230 NextTickLength(timeRemaining)
13231      long timeRemaining;
13232 {
13233     long nominalTickLength, nextTickLength;
13234
13235     if (timeRemaining > 0L && timeRemaining <= 10000L)
13236       nominalTickLength = 100L;
13237     else
13238       nominalTickLength = 1000L;
13239     nextTickLength = timeRemaining % nominalTickLength;
13240     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13241
13242     return nextTickLength;
13243 }
13244
13245 /* Adjust clock one minute up or down */
13246 void
13247 AdjustClock(Boolean which, int dir)
13248 {
13249     if(which) blackTimeRemaining += 60000*dir;
13250     else      whiteTimeRemaining += 60000*dir;
13251     DisplayBothClocks();
13252 }
13253
13254 /* Stop clocks and reset to a fresh time control */
13255 void
13256 ResetClocks() 
13257 {
13258     (void) StopClockTimer();
13259     if (appData.icsActive) {
13260         whiteTimeRemaining = blackTimeRemaining = 0;
13261     } else { /* [HGM] correct new time quote for time odds */
13262         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13263         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13264     }
13265     if (whiteFlag || blackFlag) {
13266         DisplayTitle("");
13267         whiteFlag = blackFlag = FALSE;
13268     }
13269     DisplayBothClocks();
13270 }
13271
13272 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13273
13274 /* Decrement running clock by amount of time that has passed */
13275 void
13276 DecrementClocks()
13277 {
13278     long timeRemaining;
13279     long lastTickLength, fudge;
13280     TimeMark now;
13281
13282     if (!appData.clockMode) return;
13283     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13284         
13285     GetTimeMark(&now);
13286
13287     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13288
13289     /* Fudge if we woke up a little too soon */
13290     fudge = intendedTickLength - lastTickLength;
13291     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13292
13293     if (WhiteOnMove(forwardMostMove)) {
13294         if(whiteNPS >= 0) lastTickLength = 0;
13295         timeRemaining = whiteTimeRemaining -= lastTickLength;
13296         DisplayWhiteClock(whiteTimeRemaining - fudge,
13297                           WhiteOnMove(currentMove));
13298     } else {
13299         if(blackNPS >= 0) lastTickLength = 0;
13300         timeRemaining = blackTimeRemaining -= lastTickLength;
13301         DisplayBlackClock(blackTimeRemaining - fudge,
13302                           !WhiteOnMove(currentMove));
13303     }
13304
13305     if (CheckFlags()) return;
13306         
13307     tickStartTM = now;
13308     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13309     StartClockTimer(intendedTickLength);
13310
13311     /* if the time remaining has fallen below the alarm threshold, sound the
13312      * alarm. if the alarm has sounded and (due to a takeback or time control
13313      * with increment) the time remaining has increased to a level above the
13314      * threshold, reset the alarm so it can sound again. 
13315      */
13316     
13317     if (appData.icsActive && appData.icsAlarm) {
13318
13319         /* make sure we are dealing with the user's clock */
13320         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13321                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13322            )) return;
13323
13324         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13325             alarmSounded = FALSE;
13326         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13327             PlayAlarmSound();
13328             alarmSounded = TRUE;
13329         }
13330     }
13331 }
13332
13333
13334 /* A player has just moved, so stop the previously running
13335    clock and (if in clock mode) start the other one.
13336    We redisplay both clocks in case we're in ICS mode, because
13337    ICS gives us an update to both clocks after every move.
13338    Note that this routine is called *after* forwardMostMove
13339    is updated, so the last fractional tick must be subtracted
13340    from the color that is *not* on move now.
13341 */
13342 void
13343 SwitchClocks()
13344 {
13345     long lastTickLength;
13346     TimeMark now;
13347     int flagged = FALSE;
13348
13349     GetTimeMark(&now);
13350
13351     if (StopClockTimer() && appData.clockMode) {
13352         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13353         if (WhiteOnMove(forwardMostMove)) {
13354             if(blackNPS >= 0) lastTickLength = 0;
13355             blackTimeRemaining -= lastTickLength;
13356            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13357 //         if(pvInfoList[forwardMostMove-1].time == -1)
13358                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13359                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13360         } else {
13361            if(whiteNPS >= 0) lastTickLength = 0;
13362            whiteTimeRemaining -= lastTickLength;
13363            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13364 //         if(pvInfoList[forwardMostMove-1].time == -1)
13365                  pvInfoList[forwardMostMove-1].time = 
13366                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13367         }
13368         flagged = CheckFlags();
13369     }
13370     CheckTimeControl();
13371
13372     if (flagged || !appData.clockMode) return;
13373
13374     switch (gameMode) {
13375       case MachinePlaysBlack:
13376       case MachinePlaysWhite:
13377       case BeginningOfGame:
13378         if (pausing) return;
13379         break;
13380
13381       case EditGame:
13382       case PlayFromGameFile:
13383       case IcsExamining:
13384         return;
13385
13386       default:
13387         break;
13388     }
13389
13390     tickStartTM = now;
13391     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13392       whiteTimeRemaining : blackTimeRemaining);
13393     StartClockTimer(intendedTickLength);
13394 }
13395         
13396
13397 /* Stop both clocks */
13398 void
13399 StopClocks()
13400 {       
13401     long lastTickLength;
13402     TimeMark now;
13403
13404     if (!StopClockTimer()) return;
13405     if (!appData.clockMode) return;
13406
13407     GetTimeMark(&now);
13408
13409     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13410     if (WhiteOnMove(forwardMostMove)) {
13411         if(whiteNPS >= 0) lastTickLength = 0;
13412         whiteTimeRemaining -= lastTickLength;
13413         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13414     } else {
13415         if(blackNPS >= 0) lastTickLength = 0;
13416         blackTimeRemaining -= lastTickLength;
13417         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13418     }
13419     CheckFlags();
13420 }
13421         
13422 /* Start clock of player on move.  Time may have been reset, so
13423    if clock is already running, stop and restart it. */
13424 void
13425 StartClocks()
13426 {
13427     (void) StopClockTimer(); /* in case it was running already */
13428     DisplayBothClocks();
13429     if (CheckFlags()) return;
13430
13431     if (!appData.clockMode) return;
13432     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13433
13434     GetTimeMark(&tickStartTM);
13435     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13436       whiteTimeRemaining : blackTimeRemaining);
13437
13438    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13439     whiteNPS = blackNPS = -1; 
13440     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13441        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13442         whiteNPS = first.nps;
13443     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13444        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13445         blackNPS = first.nps;
13446     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13447         whiteNPS = second.nps;
13448     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13449         blackNPS = second.nps;
13450     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13451
13452     StartClockTimer(intendedTickLength);
13453 }
13454
13455 char *
13456 TimeString(ms)
13457      long ms;
13458 {
13459     long second, minute, hour, day;
13460     char *sign = "";
13461     static char buf[32];
13462     
13463     if (ms > 0 && ms <= 9900) {
13464       /* convert milliseconds to tenths, rounding up */
13465       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13466
13467       sprintf(buf, " %03.1f ", tenths/10.0);
13468       return buf;
13469     }
13470
13471     /* convert milliseconds to seconds, rounding up */
13472     /* use floating point to avoid strangeness of integer division
13473        with negative dividends on many machines */
13474     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13475
13476     if (second < 0) {
13477         sign = "-";
13478         second = -second;
13479     }
13480     
13481     day = second / (60 * 60 * 24);
13482     second = second % (60 * 60 * 24);
13483     hour = second / (60 * 60);
13484     second = second % (60 * 60);
13485     minute = second / 60;
13486     second = second % 60;
13487     
13488     if (day > 0)
13489       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13490               sign, day, hour, minute, second);
13491     else if (hour > 0)
13492       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13493     else
13494       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13495     
13496     return buf;
13497 }
13498
13499
13500 /*
13501  * This is necessary because some C libraries aren't ANSI C compliant yet.
13502  */
13503 char *
13504 StrStr(string, match)
13505      char *string, *match;
13506 {
13507     int i, length;
13508     
13509     length = strlen(match);
13510     
13511     for (i = strlen(string) - length; i >= 0; i--, string++)
13512       if (!strncmp(match, string, length))
13513         return string;
13514     
13515     return NULL;
13516 }
13517
13518 char *
13519 StrCaseStr(string, match)
13520      char *string, *match;
13521 {
13522     int i, j, length;
13523     
13524     length = strlen(match);
13525     
13526     for (i = strlen(string) - length; i >= 0; i--, string++) {
13527         for (j = 0; j < length; j++) {
13528             if (ToLower(match[j]) != ToLower(string[j]))
13529               break;
13530         }
13531         if (j == length) return string;
13532     }
13533
13534     return NULL;
13535 }
13536
13537 #ifndef _amigados
13538 int
13539 StrCaseCmp(s1, s2)
13540      char *s1, *s2;
13541 {
13542     char c1, c2;
13543     
13544     for (;;) {
13545         c1 = ToLower(*s1++);
13546         c2 = ToLower(*s2++);
13547         if (c1 > c2) return 1;
13548         if (c1 < c2) return -1;
13549         if (c1 == NULLCHAR) return 0;
13550     }
13551 }
13552
13553
13554 int
13555 ToLower(c)
13556      int c;
13557 {
13558     return isupper(c) ? tolower(c) : c;
13559 }
13560
13561
13562 int
13563 ToUpper(c)
13564      int c;
13565 {
13566     return islower(c) ? toupper(c) : c;
13567 }
13568 #endif /* !_amigados    */
13569
13570 char *
13571 StrSave(s)
13572      char *s;
13573 {
13574     char *ret;
13575
13576     if ((ret = (char *) malloc(strlen(s) + 1))) {
13577         strcpy(ret, s);
13578     }
13579     return ret;
13580 }
13581
13582 char *
13583 StrSavePtr(s, savePtr)
13584      char *s, **savePtr;
13585 {
13586     if (*savePtr) {
13587         free(*savePtr);
13588     }
13589     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13590         strcpy(*savePtr, s);
13591     }
13592     return(*savePtr);
13593 }
13594
13595 char *
13596 PGNDate()
13597 {
13598     time_t clock;
13599     struct tm *tm;
13600     char buf[MSG_SIZ];
13601
13602     clock = time((time_t *)NULL);
13603     tm = localtime(&clock);
13604     sprintf(buf, "%04d.%02d.%02d",
13605             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13606     return StrSave(buf);
13607 }
13608
13609
13610 char *
13611 PositionToFEN(move, overrideCastling)
13612      int move;
13613      char *overrideCastling;
13614 {
13615     int i, j, fromX, fromY, toX, toY;
13616     int whiteToPlay;
13617     char buf[128];
13618     char *p, *q;
13619     int emptycount;
13620     ChessSquare piece;
13621
13622     whiteToPlay = (gameMode == EditPosition) ?
13623       !blackPlaysFirst : (move % 2 == 0);
13624     p = buf;
13625
13626     /* Piece placement data */
13627     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13628         emptycount = 0;
13629         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13630             if (boards[move][i][j] == EmptySquare) {
13631                 emptycount++;
13632             } else { ChessSquare piece = boards[move][i][j];
13633                 if (emptycount > 0) {
13634                     if(emptycount<10) /* [HGM] can be >= 10 */
13635                         *p++ = '0' + emptycount;
13636                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13637                     emptycount = 0;
13638                 }
13639                 if(PieceToChar(piece) == '+') {
13640                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13641                     *p++ = '+';
13642                     piece = (ChessSquare)(DEMOTED piece);
13643                 } 
13644                 *p++ = PieceToChar(piece);
13645                 if(p[-1] == '~') {
13646                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13647                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13648                     *p++ = '~';
13649                 }
13650             }
13651         }
13652         if (emptycount > 0) {
13653             if(emptycount<10) /* [HGM] can be >= 10 */
13654                 *p++ = '0' + emptycount;
13655             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13656             emptycount = 0;
13657         }
13658         *p++ = '/';
13659     }
13660     *(p - 1) = ' ';
13661
13662     /* [HGM] print Crazyhouse or Shogi holdings */
13663     if( gameInfo.holdingsWidth ) {
13664         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13665         q = p;
13666         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13667             piece = boards[move][i][BOARD_WIDTH-1];
13668             if( piece != EmptySquare )
13669               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13670                   *p++ = PieceToChar(piece);
13671         }
13672         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13673             piece = boards[move][BOARD_HEIGHT-i-1][0];
13674             if( piece != EmptySquare )
13675               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13676                   *p++ = PieceToChar(piece);
13677         }
13678
13679         if( q == p ) *p++ = '-';
13680         *p++ = ']';
13681         *p++ = ' ';
13682     }
13683
13684     /* Active color */
13685     *p++ = whiteToPlay ? 'w' : 'b';
13686     *p++ = ' ';
13687
13688   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13689     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13690   } else {
13691   if(nrCastlingRights) {
13692      q = p;
13693      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13694        /* [HGM] write directly from rights */
13695            if(castlingRights[move][2] >= 0 &&
13696               castlingRights[move][0] >= 0   )
13697                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13698            if(castlingRights[move][2] >= 0 &&
13699               castlingRights[move][1] >= 0   )
13700                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13701            if(castlingRights[move][5] >= 0 &&
13702               castlingRights[move][3] >= 0   )
13703                 *p++ = castlingRights[move][3] + AAA;
13704            if(castlingRights[move][5] >= 0 &&
13705               castlingRights[move][4] >= 0   )
13706                 *p++ = castlingRights[move][4] + AAA;
13707      } else {
13708
13709         /* [HGM] write true castling rights */
13710         if( nrCastlingRights == 6 ) {
13711             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13712                castlingRights[move][2] >= 0  ) *p++ = 'K';
13713             if(castlingRights[move][1] == BOARD_LEFT &&
13714                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13715             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13716                castlingRights[move][5] >= 0  ) *p++ = 'k';
13717             if(castlingRights[move][4] == BOARD_LEFT &&
13718                castlingRights[move][5] >= 0  ) *p++ = 'q';
13719         }
13720      }
13721      if (q == p) *p++ = '-'; /* No castling rights */
13722      *p++ = ' ';
13723   }
13724
13725   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13726      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13727     /* En passant target square */
13728     if (move > backwardMostMove) {
13729         fromX = moveList[move - 1][0] - AAA;
13730         fromY = moveList[move - 1][1] - ONE;
13731         toX = moveList[move - 1][2] - AAA;
13732         toY = moveList[move - 1][3] - ONE;
13733         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13734             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13735             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13736             fromX == toX) {
13737             /* 2-square pawn move just happened */
13738             *p++ = toX + AAA;
13739             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13740         } else {
13741             *p++ = '-';
13742         }
13743     } else if(move == backwardMostMove) {
13744         // [HGM] perhaps we should always do it like this, and forget the above?
13745         if(epStatus[move] >= 0) {
13746             *p++ = epStatus[move] + AAA;
13747             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13748         } else {
13749             *p++ = '-';
13750         }
13751     } else {
13752         *p++ = '-';
13753     }
13754     *p++ = ' ';
13755   }
13756   }
13757
13758     /* [HGM] find reversible plies */
13759     {   int i = 0, j=move;
13760
13761         if (appData.debugMode) { int k;
13762             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13763             for(k=backwardMostMove; k<=forwardMostMove; k++)
13764                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13765
13766         }
13767
13768         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13769         if( j == backwardMostMove ) i += initialRulePlies;
13770         sprintf(p, "%d ", i);
13771         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13772     }
13773     /* Fullmove number */
13774     sprintf(p, "%d", (move / 2) + 1);
13775     
13776     return StrSave(buf);
13777 }
13778
13779 Boolean
13780 ParseFEN(board, blackPlaysFirst, fen)
13781     Board board;
13782      int *blackPlaysFirst;
13783      char *fen;
13784 {
13785     int i, j;
13786     char *p;
13787     int emptycount;
13788     ChessSquare piece;
13789
13790     p = fen;
13791
13792     /* [HGM] by default clear Crazyhouse holdings, if present */
13793     if(gameInfo.holdingsWidth) {
13794        for(i=0; i<BOARD_HEIGHT; i++) {
13795            board[i][0]             = EmptySquare; /* black holdings */
13796            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13797            board[i][1]             = (ChessSquare) 0; /* black counts */
13798            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13799        }
13800     }
13801
13802     /* Piece placement data */
13803     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13804         j = 0;
13805         for (;;) {
13806             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13807                 if (*p == '/') p++;
13808                 emptycount = gameInfo.boardWidth - j;
13809                 while (emptycount--)
13810                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13811                 break;
13812 #if(BOARD_SIZE >= 10)
13813             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13814                 p++; emptycount=10;
13815                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13816                 while (emptycount--)
13817                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13818 #endif
13819             } else if (isdigit(*p)) {
13820                 emptycount = *p++ - '0';
13821                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13822                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13823                 while (emptycount--)
13824                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13825             } else if (*p == '+' || isalpha(*p)) {
13826                 if (j >= gameInfo.boardWidth) return FALSE;
13827                 if(*p=='+') {
13828                     piece = CharToPiece(*++p);
13829                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13830                     piece = (ChessSquare) (PROMOTED piece ); p++;
13831                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13832                 } else piece = CharToPiece(*p++);
13833
13834                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13835                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13836                     piece = (ChessSquare) (PROMOTED piece);
13837                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13838                     p++;
13839                 }
13840                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13841             } else {
13842                 return FALSE;
13843             }
13844         }
13845     }
13846     while (*p == '/' || *p == ' ') p++;
13847
13848     /* [HGM] look for Crazyhouse holdings here */
13849     while(*p==' ') p++;
13850     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13851         if(*p == '[') p++;
13852         if(*p == '-' ) *p++; /* empty holdings */ else {
13853             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13854             /* if we would allow FEN reading to set board size, we would   */
13855             /* have to add holdings and shift the board read so far here   */
13856             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13857                 *p++;
13858                 if((int) piece >= (int) BlackPawn ) {
13859                     i = (int)piece - (int)BlackPawn;
13860                     i = PieceToNumber((ChessSquare)i);
13861                     if( i >= gameInfo.holdingsSize ) return FALSE;
13862                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13863                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13864                 } else {
13865                     i = (int)piece - (int)WhitePawn;
13866                     i = PieceToNumber((ChessSquare)i);
13867                     if( i >= gameInfo.holdingsSize ) return FALSE;
13868                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13869                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13870                 }
13871             }
13872         }
13873         if(*p == ']') *p++;
13874     }
13875
13876     while(*p == ' ') p++;
13877
13878     /* Active color */
13879     switch (*p++) {
13880       case 'w':
13881         *blackPlaysFirst = FALSE;
13882         break;
13883       case 'b': 
13884         *blackPlaysFirst = TRUE;
13885         break;
13886       default:
13887         return FALSE;
13888     }
13889
13890     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13891     /* return the extra info in global variiables             */
13892
13893     /* set defaults in case FEN is incomplete */
13894     FENepStatus = EP_UNKNOWN;
13895     for(i=0; i<nrCastlingRights; i++ ) {
13896         FENcastlingRights[i] =
13897             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13898     }   /* assume possible unless obviously impossible */
13899     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13900     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13901     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13902     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13903     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13904     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13905     FENrulePlies = 0;
13906
13907     while(*p==' ') p++;
13908     if(nrCastlingRights) {
13909       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13910           /* castling indicator present, so default becomes no castlings */
13911           for(i=0; i<nrCastlingRights; i++ ) {
13912                  FENcastlingRights[i] = -1;
13913           }
13914       }
13915       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13916              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13917              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13918              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13919         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13920
13921         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13922             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13923             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13924         }
13925         switch(c) {
13926           case'K':
13927               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13928               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13929               FENcastlingRights[2] = whiteKingFile;
13930               break;
13931           case'Q':
13932               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13933               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13934               FENcastlingRights[2] = whiteKingFile;
13935               break;
13936           case'k':
13937               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13938               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13939               FENcastlingRights[5] = blackKingFile;
13940               break;
13941           case'q':
13942               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13943               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13944               FENcastlingRights[5] = blackKingFile;
13945           case '-':
13946               break;
13947           default: /* FRC castlings */
13948               if(c >= 'a') { /* black rights */
13949                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13950                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13951                   if(i == BOARD_RGHT) break;
13952                   FENcastlingRights[5] = i;
13953                   c -= AAA;
13954                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13955                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13956                   if(c > i)
13957                       FENcastlingRights[3] = c;
13958                   else
13959                       FENcastlingRights[4] = c;
13960               } else { /* white rights */
13961                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13962                     if(board[0][i] == WhiteKing) break;
13963                   if(i == BOARD_RGHT) break;
13964                   FENcastlingRights[2] = i;
13965                   c -= AAA - 'a' + 'A';
13966                   if(board[0][c] >= WhiteKing) break;
13967                   if(c > i)
13968                       FENcastlingRights[0] = c;
13969                   else
13970                       FENcastlingRights[1] = c;
13971               }
13972         }
13973       }
13974     if (appData.debugMode) {
13975         fprintf(debugFP, "FEN castling rights:");
13976         for(i=0; i<nrCastlingRights; i++)
13977         fprintf(debugFP, " %d", FENcastlingRights[i]);
13978         fprintf(debugFP, "\n");
13979     }
13980
13981       while(*p==' ') p++;
13982     }
13983
13984     /* read e.p. field in games that know e.p. capture */
13985     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13986        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13987       if(*p=='-') {
13988         p++; FENepStatus = EP_NONE;
13989       } else {
13990          char c = *p++ - AAA;
13991
13992          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13993          if(*p >= '0' && *p <='9') *p++;
13994          FENepStatus = c;
13995       }
13996     }
13997
13998
13999     if(sscanf(p, "%d", &i) == 1) {
14000         FENrulePlies = i; /* 50-move ply counter */
14001         /* (The move number is still ignored)    */
14002     }
14003
14004     return TRUE;
14005 }
14006       
14007 void
14008 EditPositionPasteFEN(char *fen)
14009 {
14010   if (fen != NULL) {
14011     Board initial_position;
14012
14013     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14014       DisplayError(_("Bad FEN position in clipboard"), 0);
14015       return ;
14016     } else {
14017       int savedBlackPlaysFirst = blackPlaysFirst;
14018       EditPositionEvent();
14019       blackPlaysFirst = savedBlackPlaysFirst;
14020       CopyBoard(boards[0], initial_position);
14021           /* [HGM] copy FEN attributes as well */
14022           {   int i;
14023               initialRulePlies = FENrulePlies;
14024               epStatus[0] = FENepStatus;
14025               for( i=0; i<nrCastlingRights; i++ )
14026                   castlingRights[0][i] = FENcastlingRights[i];
14027           }
14028       EditPositionDone();
14029       DisplayBothClocks();
14030       DrawPosition(FALSE, boards[currentMove]);
14031     }
14032   }
14033 }
14034
14035 static char cseq[12] = "\\   ";
14036
14037 Boolean set_cont_sequence(char *new_seq)
14038 {
14039     int len;
14040     Boolean ret;
14041
14042     // handle bad attempts to set the sequence
14043         if (!new_seq)
14044                 return 0; // acceptable error - no debug
14045
14046     len = strlen(new_seq);
14047     ret = (len > 0) && (len < sizeof(cseq));
14048     if (ret)
14049         strcpy(cseq, new_seq);
14050     else if (appData.debugMode)
14051         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14052     return ret;
14053 }
14054
14055 /*
14056     reformat a source message so words don't cross the width boundary.  internal
14057     newlines are not removed.  returns the wrapped size (no null character unless
14058     included in source message).  If dest is NULL, only calculate the size required
14059     for the dest buffer.  lp argument indicats line position upon entry, and it's
14060     passed back upon exit.
14061 */
14062 int wrap(char *dest, char *src, int count, int width, int *lp)
14063 {
14064     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14065
14066     cseq_len = strlen(cseq);
14067     old_line = line = *lp;
14068     ansi = len = clen = 0;
14069
14070     for (i=0; i < count; i++)
14071     {
14072         if (src[i] == '\033')
14073             ansi = 1;
14074
14075         // if we hit the width, back up
14076         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14077         {
14078             // store i & len in case the word is too long
14079             old_i = i, old_len = len;
14080
14081             // find the end of the last word
14082             while (i && src[i] != ' ' && src[i] != '\n')
14083             {
14084                 i--;
14085                 len--;
14086             }
14087
14088             // word too long?  restore i & len before splitting it
14089             if ((old_i-i+clen) >= width)
14090             {
14091                 i = old_i;
14092                 len = old_len;
14093             }
14094
14095             // extra space?
14096             if (i && src[i-1] == ' ')
14097                 len--;
14098
14099             if (src[i] != ' ' && src[i] != '\n')
14100             {
14101                 i--;
14102                 if (len)
14103                     len--;
14104             }
14105
14106             // now append the newline and continuation sequence
14107             if (dest)
14108                 dest[len] = '\n';
14109             len++;
14110             if (dest)
14111                 strncpy(dest+len, cseq, cseq_len);
14112             len += cseq_len;
14113             line = cseq_len;
14114             clen = cseq_len;
14115             continue;
14116         }
14117
14118         if (dest)
14119             dest[len] = src[i];
14120         len++;
14121         if (!ansi)
14122             line++;
14123         if (src[i] == '\n')
14124             line = 0;
14125         if (src[i] == 'm')
14126             ansi = 0;
14127     }
14128     if (dest && appData.debugMode)
14129     {
14130         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14131             count, width, line, len, *lp);
14132         show_bytes(debugFP, src, count);
14133         fprintf(debugFP, "\ndest: ");
14134         show_bytes(debugFP, dest, len);
14135         fprintf(debugFP, "\n");
14136     }
14137     *lp = dest ? line : old_line;
14138
14139     return len;
14140 }