improved mouse handler
[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
1945    startedFromPositionFile = FALSE;
1946    if(gameInfo.variant == newVariant) return;
1947
1948    /* [HGM] This routine is called each time an assignment is made to
1949     * gameInfo.variant during a game, to make sure the board sizes
1950     * are set to match the new variant. If that means adding or deleting
1951     * holdings, we shift the playing board accordingly
1952     * This kludge is needed because in ICS observe mode, we get boards
1953     * of an ongoing game without knowing the variant, and learn about the
1954     * latter only later. This can be because of the move list we requested,
1955     * in which case the game history is refilled from the beginning anyway,
1956     * but also when receiving holdings of a crazyhouse game. In the latter
1957     * case we want to add those holdings to the already received position.
1958     */
1959
1960    
1961    if (appData.debugMode) {
1962      fprintf(debugFP, "Switch board from %s to %s\n",
1963              VariantName(gameInfo.variant), VariantName(newVariant));
1964      setbuf(debugFP, NULL);
1965    }
1966    shuffleOpenings = 0;       /* [HGM] shuffle */
1967    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1968    switch(newVariant) 
1969      {
1970      case VariantShogi:
1971        newWidth = 9;  newHeight = 9;
1972        gameInfo.holdingsSize = 7;
1973      case VariantBughouse:
1974      case VariantCrazyhouse:
1975        newHoldingsWidth = 2; break;
1976      case VariantGreat:
1977        newWidth = 10;
1978      case VariantSuper:
1979        newHoldingsWidth = 2;
1980        gameInfo.holdingsSize = 8;
1981        break;
1982      case VariantGothic:
1983      case VariantCapablanca:
1984      case VariantCapaRandom:
1985        newWidth = 10;
1986      default:
1987        newHoldingsWidth = gameInfo.holdingsSize = 0;
1988      };
1989    
1990    if(newWidth  != gameInfo.boardWidth  ||
1991       newHeight != gameInfo.boardHeight ||
1992       newHoldingsWidth != gameInfo.holdingsWidth ) {
1993      
1994      /* shift position to new playing area, if needed */
1995      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996        for(i=0; i<BOARD_HEIGHT; i++) 
1997          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999              board[i][j];
2000        for(i=0; i<newHeight; i++) {
2001          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003        }
2004      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005        for(i=0; i<BOARD_HEIGHT; i++)
2006          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008              board[i][j];
2009      }
2010      gameInfo.boardWidth  = newWidth;
2011      gameInfo.boardHeight = newHeight;
2012      gameInfo.holdingsWidth = newHoldingsWidth;
2013      gameInfo.variant = newVariant;
2014      InitDrawingSizes(-2, 0);
2015      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2016    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\   ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2031
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2034
2035 void
2036 read_from_ics(isr, closure, data, count, error)
2037      InputSourceRef isr;
2038      VOIDSTAR closure;
2039      char *data;
2040      int count;
2041      int error;
2042 {
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2052     
2053     static int started = STARTED_NONE;
2054     static char parse[20000];
2055     static int parse_pos = 0;
2056     static char buf[BUF_SIZE + 1];
2057     static int firstTime = TRUE, intfSet = FALSE;
2058     static ColorClass prevColor = ColorNormal;
2059     static int savingComment = FALSE;
2060     static int cmatch = 0; // continuation sequence match
2061     char *bp;
2062     char str[500];
2063     int i, oldi;
2064     int buf_len;
2065     int next_out;
2066     int tkind;
2067     int backup;    /* [DM] For zippy color lines */
2068     char *p;
2069     char talker[MSG_SIZ]; // [HGM] chat
2070     int channel;
2071
2072     if (appData.debugMode) {
2073       if (!error) {
2074         fprintf(debugFP, "<ICS: ");
2075         show_bytes(debugFP, data, count);
2076         fprintf(debugFP, "\n");
2077       }
2078     }
2079
2080     if (appData.debugMode) { int f = forwardMostMove;
2081         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2083     }
2084     if (count > 0) {
2085         /* If last read ended with a partial line that we couldn't parse,
2086            prepend it to the new read and try again. */
2087         if (leftover_len > 0) {
2088             for (i=0; i<leftover_len; i++)
2089               buf[i] = buf[leftover_start + i];
2090         }
2091
2092     /* copy new characters into the buffer */
2093     bp = buf + leftover_len;
2094     buf_len=leftover_len;
2095     for (i=0; i<count; i++)
2096     {
2097         // ignore these
2098         if (data[i] == '\r')
2099             continue;
2100
2101         // join lines split by ICS?
2102         if (!appData.noJoin)
2103         {
2104             /*
2105                 Joining just consists of finding matches against the
2106                 continuation sequence, and discarding that sequence
2107                 if found instead of copying it.  So, until a match
2108                 fails, there's nothing to do since it might be the
2109                 complete sequence, and thus, something we don't want
2110                 copied.
2111             */
2112             if (data[i] == cont_seq[cmatch])
2113             {
2114                 cmatch++;
2115                 if (cmatch == strlen(cont_seq))
2116                 {
2117                     cmatch = 0; // complete match.  just reset the counter
2118
2119                     /*
2120                         it's possible for the ICS to not include the space
2121                         at the end of the last word, making our [correct]
2122                         join operation fuse two separate words.  the server
2123                         does this when the space occurs at the width setting.
2124                     */
2125                     if (!buf_len || buf[buf_len-1] != ' ')
2126                     {
2127                         *bp++ = ' ';
2128                         buf_len++;
2129                     }
2130                 }
2131                 continue;
2132             }
2133             else if (cmatch)
2134             {
2135                 /*
2136                     match failed, so we have to copy what matched before
2137                     falling through and copying this character.  In reality,
2138                     this will only ever be just the newline character, but
2139                     it doesn't hurt to be precise.
2140                 */
2141                 strncpy(bp, cont_seq, cmatch);
2142                 bp += cmatch;
2143                 buf_len += cmatch;
2144                 cmatch = 0;
2145             }
2146         }
2147
2148         // copy this char
2149         *bp++ = data[i];
2150         buf_len++;
2151     }
2152
2153         buf[buf_len] = NULLCHAR;
2154         next_out = leftover_len;
2155         leftover_start = 0;
2156         
2157         i = 0;
2158         while (i < buf_len) {
2159             /* Deal with part of the TELNET option negotiation
2160                protocol.  We refuse to do anything beyond the
2161                defaults, except that we allow the WILL ECHO option,
2162                which ICS uses to turn off password echoing when we are
2163                directly connected to it.  We reject this option
2164                if localLineEditing mode is on (always on in xboard)
2165                and we are talking to port 23, which might be a real
2166                telnet server that will try to keep WILL ECHO on permanently.
2167              */
2168             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170                 unsigned char option;
2171                 oldi = i;
2172                 switch ((unsigned char) buf[++i]) {
2173                   case TN_WILL:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<WILL ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       case TN_ECHO:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "ECHO ");
2180                         /* Reply only if this is a change, according
2181                            to the protocol rules. */
2182                         if (remoteEchoOption) break;
2183                         if (appData.localLineEditing &&
2184                             atoi(appData.icsPort) == TN_PORT) {
2185                             TelnetRequest(TN_DONT, TN_ECHO);
2186                         } else {
2187                             EchoOff();
2188                             TelnetRequest(TN_DO, TN_ECHO);
2189                             remoteEchoOption = TRUE;
2190                         }
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         /* Whatever this is, we don't want it. */
2196                         TelnetRequest(TN_DONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_WONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (!remoteEchoOption) break;
2210                         EchoOn();
2211                         TelnetRequest(TN_DONT, TN_ECHO);
2212                         remoteEchoOption = FALSE;
2213                         break;
2214                       default:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", (unsigned char) option);
2217                         /* Whatever this is, it must already be turned
2218                            off, because we never agree to turn on
2219                            anything non-default, so according to the
2220                            protocol rules, we don't reply. */
2221                         break;
2222                     }
2223                     break;
2224                   case TN_DO:
2225                     if (appData.debugMode)
2226                       fprintf(debugFP, "\n<DO ");
2227                     switch (option = (unsigned char) buf[++i]) {
2228                       default:
2229                         /* Whatever this is, we refuse to do it. */
2230                         if (appData.debugMode)
2231                           fprintf(debugFP, "%d ", option);
2232                         TelnetRequest(TN_WONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_DONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<DONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         /* Whatever this is, we are already not doing
2244                            it, because we never agree to do anything
2245                            non-default, so according to the protocol
2246                            rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_IAC:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<IAC ");
2253                     /* Doubled IAC; pass it through */
2254                     i--;
2255                     break;
2256                   default:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259                     /* Drop all other telnet commands on the floor */
2260                     break;
2261                 }
2262                 if (oldi > next_out)
2263                   SendToPlayer(&buf[next_out], oldi - next_out);
2264                 if (++i > next_out)
2265                   next_out = i;
2266                 continue;
2267             }
2268                 
2269             /* OK, this at least will *usually* work */
2270             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2271                 loggedOn = TRUE;
2272             }
2273             
2274             if (loggedOn && !intfSet) {
2275                 if (ics_type == ICS_ICC) {
2276                   sprintf(str,
2277                           "/set-quietly interface %s\n/set-quietly style 12\n",
2278                           programVersion);
2279                 } else if (ics_type == ICS_CHESSNET) {
2280                   sprintf(str, "/style 12\n");
2281                 } else {
2282                   strcpy(str, "alias $ @\n$set interface ");
2283                   strcat(str, programVersion);
2284                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 #ifdef WIN32
2286                   strcat(str, "$iset nohighlight 1\n");
2287 #endif
2288                   strcat(str, "$iset lock 1\n$style 12\n");
2289                 }
2290                 SendToICS(str);
2291                 NotifyFrontendLogin();
2292                 intfSet = TRUE;
2293             }
2294
2295             if (started == STARTED_COMMENT) {
2296                 /* Accumulate characters in comment */
2297                 parse[parse_pos++] = buf[i];
2298                 if (buf[i] == '\n') {
2299                     parse[parse_pos] = NULLCHAR;
2300                     if(chattingPartner>=0) {
2301                         char mess[MSG_SIZ];
2302                         sprintf(mess, "%s%s", talker, parse);
2303                         OutputChatMessage(chattingPartner, mess);
2304                         chattingPartner = -1;
2305                     } else
2306                     if(!suppressKibitz) // [HGM] kibitz
2307                         AppendComment(forwardMostMove, StripHighlight(parse));
2308                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309                         int nrDigit = 0, nrAlph = 0, i;
2310                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312                         parse[parse_pos] = NULLCHAR;
2313                         // try to be smart: if it does not look like search info, it should go to
2314                         // ICS interaction window after all, not to engine-output window.
2315                         for(i=0; i<parse_pos; i++) { // count letters and digits
2316                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2318                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2319                         }
2320                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321                             int depth=0; float score;
2322                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324                                 pvInfoList[forwardMostMove-1].depth = depth;
2325                                 pvInfoList[forwardMostMove-1].score = 100*score;
2326                             }
2327                             OutputKibitz(suppressKibitz, parse);
2328                         } else {
2329                             char tmp[MSG_SIZ];
2330                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331                             SendToPlayer(tmp, strlen(tmp));
2332                         }
2333                     }
2334                     started = STARTED_NONE;
2335                 } else {
2336                     /* Don't match patterns against characters in chatter */
2337                     i++;
2338                     continue;
2339                 }
2340             }
2341             if (started == STARTED_CHATTER) {
2342                 if (buf[i] != '\n') {
2343                     /* Don't match patterns against characters in chatter */
2344                     i++;
2345                     continue;
2346                 }
2347                 started = STARTED_NONE;
2348             }
2349
2350             /* Kludge to deal with rcmd protocol */
2351             if (firstTime && looking_at(buf, &i, "\001*")) {
2352                 DisplayFatalError(&buf[1], 0, 1);
2353                 continue;
2354             } else {
2355                 firstTime = FALSE;
2356             }
2357
2358             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2359                 ics_type = ICS_ICC;
2360                 ics_prefix = "/";
2361                 if (appData.debugMode)
2362                   fprintf(debugFP, "ics_type %d\n", ics_type);
2363                 continue;
2364             }
2365             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366                 ics_type = ICS_FICS;
2367                 ics_prefix = "$";
2368                 if (appData.debugMode)
2369                   fprintf(debugFP, "ics_type %d\n", ics_type);
2370                 continue;
2371             }
2372             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373                 ics_type = ICS_CHESSNET;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379
2380             if (!loggedOn &&
2381                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2383                  looking_at(buf, &i, "will be \"*\""))) {
2384               strcpy(ics_handle, star_match[0]);
2385               continue;
2386             }
2387
2388             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389               char buf[MSG_SIZ];
2390               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391               DisplayIcsInteractionTitle(buf);
2392               have_set_title = TRUE;
2393             }
2394
2395             /* skip finger notes */
2396             if (started == STARTED_NONE &&
2397                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398                  (buf[i] == '1' && buf[i+1] == '0')) &&
2399                 buf[i+2] == ':' && buf[i+3] == ' ') {
2400               started = STARTED_CHATTER;
2401               i += 3;
2402               continue;
2403             }
2404
2405             /* skip formula vars */
2406             if (started == STARTED_NONE &&
2407                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408               started = STARTED_CHATTER;
2409               i += 3;
2410               continue;
2411             }
2412
2413             oldi = i;
2414             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415             if (appData.autoKibitz && started == STARTED_NONE && 
2416                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2417                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418                 if(looking_at(buf, &i, "* kibitzes: ") &&
2419                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2420                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2421                         suppressKibitz = TRUE;
2422                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423                                 && (gameMode == IcsPlayingWhite)) ||
2424                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2426                             started = STARTED_CHATTER; // own kibitz we simply discard
2427                         else {
2428                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429                             parse_pos = 0; parse[0] = NULLCHAR;
2430                             savingComment = TRUE;
2431                             suppressKibitz = gameMode != IcsObserving ? 2 :
2432                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2433                         } 
2434                         continue;
2435                 } else
2436                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437                     started = STARTED_CHATTER;
2438                     suppressKibitz = TRUE;
2439                 }
2440             } // [HGM] kibitz: end of patch
2441
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443
2444             // [HGM] chat: intercept tells by users for which we have an open chat window
2445             channel = -1;
2446             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2447                                            looking_at(buf, &i, "* whispers:") ||
2448                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450                 int p;
2451                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452                 chattingPartner = -1;
2453
2454                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455                 for(p=0; p<MAX_CHAT; p++) {
2456                     if(channel == atoi(chatPartner[p])) {
2457                     talker[0] = '['; strcat(talker, "]");
2458                     chattingPartner = p; break;
2459                     }
2460                 } else
2461                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462                 for(p=0; p<MAX_CHAT; p++) {
2463                     if(!strcmp("WHISPER", chatPartner[p])) {
2464                         talker[0] = '['; strcat(talker, "]");
2465                         chattingPartner = p; break;
2466                     }
2467                 }
2468                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470                     talker[0] = 0;
2471                     chattingPartner = p; break;
2472                 }
2473                 if(chattingPartner<0) i = oldi; else {
2474                     started = STARTED_COMMENT;
2475                     parse_pos = 0; parse[0] = NULLCHAR;
2476                     savingComment = TRUE;
2477                     suppressKibitz = TRUE;
2478                 }
2479             } // [HGM] chat: end of patch
2480
2481             if (appData.zippyTalk || appData.zippyPlay) {
2482                 /* [DM] Backup address for color zippy lines */
2483                 backup = i;
2484 #if ZIPPY
2485        #ifdef WIN32
2486                if (loggedOn == TRUE)
2487                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489        #else
2490                 if (ZippyControl(buf, &i) ||
2491                     ZippyConverse(buf, &i) ||
2492                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493                       loggedOn = TRUE;
2494                       if (!appData.colorize) continue;
2495                 }
2496        #endif
2497 #endif
2498             } // [DM] 'else { ' deleted
2499                 if (
2500                     /* Regular tells and says */
2501                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2503                     looking_at(buf, &i, "* says: ") ||
2504                     /* Don't color "message" or "messages" output */
2505                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506                     looking_at(buf, &i, "*. * at *:*: ") ||
2507                     looking_at(buf, &i, "--* (*:*): ") ||
2508                     /* Message notifications (same color as tells) */
2509                     looking_at(buf, &i, "* has left a message ") ||
2510                     looking_at(buf, &i, "* just sent you a message:\n") ||
2511                     /* Whispers and kibitzes */
2512                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513                     looking_at(buf, &i, "* kibitzes: ") ||
2514                     /* Channel tells */
2515                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516
2517                   if (tkind == 1 && strchr(star_match[0], ':')) {
2518                       /* Avoid "tells you:" spoofs in channels */
2519                      tkind = 3;
2520                   }
2521                   if (star_match[0][0] == NULLCHAR ||
2522                       strchr(star_match[0], ' ') ||
2523                       (tkind == 3 && strchr(star_match[1], ' '))) {
2524                     /* Reject bogus matches */
2525                     i = oldi;
2526                   } else {
2527                     if (appData.colorize) {
2528                       if (oldi > next_out) {
2529                         SendToPlayer(&buf[next_out], oldi - next_out);
2530                         next_out = oldi;
2531                       }
2532                       switch (tkind) {
2533                       case 1:
2534                         Colorize(ColorTell, FALSE);
2535                         curColor = ColorTell;
2536                         break;
2537                       case 2:
2538                         Colorize(ColorKibitz, FALSE);
2539                         curColor = ColorKibitz;
2540                         break;
2541                       case 3:
2542                         p = strrchr(star_match[1], '(');
2543                         if (p == NULL) {
2544                           p = star_match[1];
2545                         } else {
2546                           p++;
2547                         }
2548                         if (atoi(p) == 1) {
2549                           Colorize(ColorChannel1, FALSE);
2550                           curColor = ColorChannel1;
2551                         } else {
2552                           Colorize(ColorChannel, FALSE);
2553                           curColor = ColorChannel;
2554                         }
2555                         break;
2556                       case 5:
2557                         curColor = ColorNormal;
2558                         break;
2559                       }
2560                     }
2561                     if (started == STARTED_NONE && appData.autoComment &&
2562                         (gameMode == IcsObserving ||
2563                          gameMode == IcsPlayingWhite ||
2564                          gameMode == IcsPlayingBlack)) {
2565                       parse_pos = i - oldi;
2566                       memcpy(parse, &buf[oldi], parse_pos);
2567                       parse[parse_pos] = NULLCHAR;
2568                       started = STARTED_COMMENT;
2569                       savingComment = TRUE;
2570                     } else {
2571                       started = STARTED_CHATTER;
2572                       savingComment = FALSE;
2573                     }
2574                     loggedOn = TRUE;
2575                     continue;
2576                   }
2577                 }
2578
2579                 if (looking_at(buf, &i, "* s-shouts: ") ||
2580                     looking_at(buf, &i, "* c-shouts: ")) {
2581                     if (appData.colorize) {
2582                         if (oldi > next_out) {
2583                             SendToPlayer(&buf[next_out], oldi - next_out);
2584                             next_out = oldi;
2585                         }
2586                         Colorize(ColorSShout, FALSE);
2587                         curColor = ColorSShout;
2588                     }
2589                     loggedOn = TRUE;
2590                     started = STARTED_CHATTER;
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "--->")) {
2595                     loggedOn = TRUE;
2596                     continue;
2597                 }
2598
2599                 if (looking_at(buf, &i, "* shouts: ") ||
2600                     looking_at(buf, &i, "--> ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorShout, FALSE);
2607                         curColor = ColorShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at( buf, &i, "Challenge:")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorChallenge, FALSE);
2621                         curColor = ColorChallenge;
2622                     }
2623                     loggedOn = TRUE;
2624                     continue;
2625                 }
2626
2627                 if (looking_at(buf, &i, "* offers you") ||
2628                     looking_at(buf, &i, "* offers to be") ||
2629                     looking_at(buf, &i, "* would like to") ||
2630                     looking_at(buf, &i, "* requests to") ||
2631                     looking_at(buf, &i, "Your opponent offers") ||
2632                     looking_at(buf, &i, "Your opponent requests")) {
2633
2634                     if (appData.colorize) {
2635                         if (oldi > next_out) {
2636                             SendToPlayer(&buf[next_out], oldi - next_out);
2637                             next_out = oldi;
2638                         }
2639                         Colorize(ColorRequest, FALSE);
2640                         curColor = ColorRequest;
2641                     }
2642                     continue;
2643                 }
2644
2645                 if (looking_at(buf, &i, "* (*) seeking")) {
2646                     if (appData.colorize) {
2647                         if (oldi > next_out) {
2648                             SendToPlayer(&buf[next_out], oldi - next_out);
2649                             next_out = oldi;
2650                         }
2651                         Colorize(ColorSeek, FALSE);
2652                         curColor = ColorSeek;
2653                     }
2654                     continue;
2655             }
2656
2657             if (looking_at(buf, &i, "\\   ")) {
2658                 if (prevColor != ColorNormal) {
2659                     if (oldi > next_out) {
2660                         SendToPlayer(&buf[next_out], oldi - next_out);
2661                         next_out = oldi;
2662                     }
2663                     Colorize(prevColor, TRUE);
2664                     curColor = prevColor;
2665                 }
2666                 if (savingComment) {
2667                     parse_pos = i - oldi;
2668                     memcpy(parse, &buf[oldi], parse_pos);
2669                     parse[parse_pos] = NULLCHAR;
2670                     started = STARTED_COMMENT;
2671                 } else {
2672                     started = STARTED_CHATTER;
2673                 }
2674                 continue;
2675             }
2676
2677             if (looking_at(buf, &i, "Black Strength :") ||
2678                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679                 looking_at(buf, &i, "<10>") ||
2680                 looking_at(buf, &i, "#@#")) {
2681                 /* Wrong board style */
2682                 loggedOn = TRUE;
2683                 SendToICS(ics_prefix);
2684                 SendToICS("set style 12\n");
2685                 SendToICS(ics_prefix);
2686                 SendToICS("refresh\n");
2687                 continue;
2688             }
2689             
2690             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691                 ICSInitScript();
2692                 have_sent_ICS_logon = 1;
2693                 continue;
2694             }
2695               
2696             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2697                 (looking_at(buf, &i, "\n<12> ") ||
2698                  looking_at(buf, &i, "<12> "))) {
2699                 loggedOn = TRUE;
2700                 if (oldi > next_out) {
2701                     SendToPlayer(&buf[next_out], oldi - next_out);
2702                 }
2703                 next_out = i;
2704                 started = STARTED_BOARD;
2705                 parse_pos = 0;
2706                 continue;
2707             }
2708
2709             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710                 looking_at(buf, &i, "<b1> ")) {
2711                 if (oldi > next_out) {
2712                     SendToPlayer(&buf[next_out], oldi - next_out);
2713                 }
2714                 next_out = i;
2715                 started = STARTED_HOLDINGS;
2716                 parse_pos = 0;
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721                 loggedOn = TRUE;
2722                 /* Header for a move list -- first line */
2723
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     switch (gameMode) {
2727                       case IcsIdle:
2728                       case BeginningOfGame:
2729                         /* User typed "moves" or "oldmoves" while we
2730                            were idle.  Pretend we asked for these
2731                            moves and soak them up so user can step
2732                            through them and/or save them.
2733                            */
2734                         Reset(FALSE, TRUE);
2735                         gameMode = IcsObserving;
2736                         ModeHighlight();
2737                         ics_gamenum = -1;
2738                         ics_getting_history = H_GOT_UNREQ_HEADER;
2739                         break;
2740                       case EditGame: /*?*/
2741                       case EditPosition: /*?*/
2742                         /* Should above feature work in these modes too? */
2743                         /* For now it doesn't */
2744                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2745                         break;
2746                       default:
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                     }
2750                     break;
2751                   case H_REQUESTED:
2752                     /* Is this the right one? */
2753                     if (gameInfo.white && gameInfo.black &&
2754                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2755                         strcmp(gameInfo.black, star_match[2]) == 0) {
2756                         /* All is well */
2757                         ics_getting_history = H_GOT_REQ_HEADER;
2758                     }
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                   case H_GOT_UNREQ_HEADER:
2762                   case H_GOT_UNWANTED_HEADER:
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: two headers"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                 }
2769
2770                 /* Save player ratings into gameInfo if needed */
2771                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773                     (gameInfo.whiteRating == -1 ||
2774                      gameInfo.blackRating == -1)) {
2775
2776                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2777                     gameInfo.blackRating = string_to_rating(star_match[3]);
2778                     if (appData.debugMode)
2779                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2780                               gameInfo.whiteRating, gameInfo.blackRating);
2781                 }
2782                 continue;
2783             }
2784
2785             if (looking_at(buf, &i,
2786               "* * match, initial time: * minute*, increment: * second")) {
2787                 /* Header for a move list -- second line */
2788                 /* Initial board will follow if this is a wild game */
2789                 if (gameInfo.event != NULL) free(gameInfo.event);
2790                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791                 gameInfo.event = StrSave(str);
2792                 /* [HGM] we switched variant. Translate boards if needed. */
2793                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i, "Move  ")) {
2798                 /* Beginning of a move list */
2799                 switch (ics_getting_history) {
2800                   case H_FALSE:
2801                     /* Normally should not happen */
2802                     /* Maybe user hit reset while we were parsing */
2803                     break;
2804                   case H_REQUESTED:
2805                     /* Happens if we are ignoring a move list that is not
2806                      * the one we just requested.  Common if the user
2807                      * tries to observe two games without turning off
2808                      * getMoveList */
2809                     break;
2810                   case H_GETTING_MOVES:
2811                     /* Should not happen */
2812                     DisplayError(_("Error gathering move list: nested"), 0);
2813                     ics_getting_history = H_FALSE;
2814                     break;
2815                   case H_GOT_REQ_HEADER:
2816                     ics_getting_history = H_GETTING_MOVES;
2817                     started = STARTED_MOVES;
2818                     parse_pos = 0;
2819                     if (oldi > next_out) {
2820                         SendToPlayer(&buf[next_out], oldi - next_out);
2821                     }
2822                     break;
2823                   case H_GOT_UNREQ_HEADER:
2824                     ics_getting_history = H_GETTING_MOVES;
2825                     started = STARTED_MOVES_NOHIDE;
2826                     parse_pos = 0;
2827                     break;
2828                   case H_GOT_UNWANTED_HEADER:
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                 }
2832                 continue;
2833             }                           
2834             
2835             if (looking_at(buf, &i, "% ") ||
2836                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838                 savingComment = FALSE;
2839                 switch (started) {
2840                   case STARTED_MOVES:
2841                   case STARTED_MOVES_NOHIDE:
2842                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843                     parse[parse_pos + i - oldi] = NULLCHAR;
2844                     ParseGameHistory(parse);
2845 #if ZIPPY
2846                     if (appData.zippyPlay && first.initDone) {
2847                         FeedMovesToProgram(&first, forwardMostMove);
2848                         if (gameMode == IcsPlayingWhite) {
2849                             if (WhiteOnMove(forwardMostMove)) {
2850                                 if (first.sendTime) {
2851                                   if (first.useColors) {
2852                                     SendToProgram("black\n", &first); 
2853                                   }
2854                                   SendTimeRemaining(&first, TRUE);
2855                                 }
2856                                 if (first.useColors) {
2857                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858                                 }
2859                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860                                 first.maybeThinking = TRUE;
2861                             } else {
2862                                 if (first.usePlayother) {
2863                                   if (first.sendTime) {
2864                                     SendTimeRemaining(&first, TRUE);
2865                                   }
2866                                   SendToProgram("playother\n", &first);
2867                                   firstMove = FALSE;
2868                                 } else {
2869                                   firstMove = TRUE;
2870                                 }
2871                             }
2872                         } else if (gameMode == IcsPlayingBlack) {
2873                             if (!WhiteOnMove(forwardMostMove)) {
2874                                 if (first.sendTime) {
2875                                   if (first.useColors) {
2876                                     SendToProgram("white\n", &first);
2877                                   }
2878                                   SendTimeRemaining(&first, FALSE);
2879                                 }
2880                                 if (first.useColors) {
2881                                   SendToProgram("black\n", &first);
2882                                 }
2883                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884                                 first.maybeThinking = TRUE;
2885                             } else {
2886                                 if (first.usePlayother) {
2887                                   if (first.sendTime) {
2888                                     SendTimeRemaining(&first, FALSE);
2889                                   }
2890                                   SendToProgram("playother\n", &first);
2891                                   firstMove = FALSE;
2892                                 } else {
2893                                   firstMove = TRUE;
2894                                 }
2895                             }
2896                         }                       
2897                     }
2898 #endif
2899                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2900                         /* Moves came from oldmoves or moves command
2901                            while we weren't doing anything else.
2902                            */
2903                         currentMove = forwardMostMove;
2904                         ClearHighlights();/*!!could figure this out*/
2905                         flipView = appData.flipView;
2906                         DrawPosition(TRUE, boards[currentMove]);
2907                         DisplayBothClocks();
2908                         sprintf(str, "%s vs. %s",
2909                                 gameInfo.white, gameInfo.black);
2910                         DisplayTitle(str);
2911                         gameMode = IcsIdle;
2912                     } else {
2913                         /* Moves were history of an active game */
2914                         if (gameInfo.resultDetails != NULL) {
2915                             free(gameInfo.resultDetails);
2916                             gameInfo.resultDetails = NULL;
2917                         }
2918                     }
2919                     HistorySet(parseList, backwardMostMove,
2920                                forwardMostMove, currentMove-1);
2921                     DisplayMove(currentMove - 1);
2922                     if (started == STARTED_MOVES) next_out = i;
2923                     started = STARTED_NONE;
2924                     ics_getting_history = H_FALSE;
2925                     break;
2926
2927                   case STARTED_OBSERVE:
2928                     started = STARTED_NONE;
2929                     SendToICS(ics_prefix);
2930                     SendToICS("refresh\n");
2931                     break;
2932
2933                   default:
2934                     break;
2935                 }
2936                 if(bookHit) { // [HGM] book: simulate book reply
2937                     static char bookMove[MSG_SIZ]; // a bit generous?
2938
2939                     programStats.nodes = programStats.depth = programStats.time = 
2940                     programStats.score = programStats.got_only_move = 0;
2941                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942
2943                     strcpy(bookMove, "move ");
2944                     strcat(bookMove, bookHit);
2945                     HandleMachineMove(bookMove, &first);
2946                 }
2947                 continue;
2948             }
2949             
2950             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951                  started == STARTED_HOLDINGS ||
2952                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953                 /* Accumulate characters in move list or board */
2954                 parse[parse_pos++] = buf[i];
2955             }
2956             
2957             /* Start of game messages.  Mostly we detect start of game
2958                when the first board image arrives.  On some versions
2959                of the ICS, though, we need to do a "refresh" after starting
2960                to observe in order to get the current board right away. */
2961             if (looking_at(buf, &i, "Adding game * to observation list")) {
2962                 started = STARTED_OBSERVE;
2963                 continue;
2964             }
2965
2966             /* Handle auto-observe */
2967             if (appData.autoObserve &&
2968                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970                 char *player;
2971                 /* Choose the player that was highlighted, if any. */
2972                 if (star_match[0][0] == '\033' ||
2973                     star_match[1][0] != '\033') {
2974                     player = star_match[0];
2975                 } else {
2976                     player = star_match[2];
2977                 }
2978                 sprintf(str, "%sobserve %s\n",
2979                         ics_prefix, StripHighlightAndTitle(player));
2980                 SendToICS(str);
2981
2982                 /* Save ratings from notify string */
2983                 strcpy(player1Name, star_match[0]);
2984                 player1Rating = string_to_rating(star_match[1]);
2985                 strcpy(player2Name, star_match[2]);
2986                 player2Rating = string_to_rating(star_match[3]);
2987
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, 
2990                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2991                           player1Name, player1Rating,
2992                           player2Name, player2Rating);
2993
2994                 continue;
2995             }
2996
2997             /* Deal with automatic examine mode after a game,
2998                and with IcsObserving -> IcsExamining transition */
2999             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000                 looking_at(buf, &i, "has made you an examiner of game *")) {
3001
3002                 int gamenum = atoi(star_match[0]);
3003                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004                     gamenum == ics_gamenum) {
3005                     /* We were already playing or observing this game;
3006                        no need to refetch history */
3007                     gameMode = IcsExamining;
3008                     if (pausing) {
3009                         pauseExamForwardMostMove = forwardMostMove;
3010                     } else if (currentMove < forwardMostMove) {
3011                         ForwardInner(forwardMostMove);
3012                     }
3013                 } else {
3014                     /* I don't think this case really can happen */
3015                     SendToICS(ics_prefix);
3016                     SendToICS("refresh\n");
3017                 }
3018                 continue;
3019             }    
3020             
3021             /* Error messages */
3022 //          if (ics_user_moved) {
3023             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024                 if (looking_at(buf, &i, "Illegal move") ||
3025                     looking_at(buf, &i, "Not a legal move") ||
3026                     looking_at(buf, &i, "Your king is in check") ||
3027                     looking_at(buf, &i, "It isn't your turn") ||
3028                     looking_at(buf, &i, "It is not your move")) {
3029                     /* Illegal move */
3030                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031                         currentMove = --forwardMostMove;
3032                         DisplayMove(currentMove - 1); /* before DMError */
3033                         DrawPosition(FALSE, boards[currentMove]);
3034                         SwitchClocks();
3035                         DisplayBothClocks();
3036                     }
3037                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038                     ics_user_moved = 0;
3039                     continue;
3040                 }
3041             }
3042
3043             if (looking_at(buf, &i, "still have time") ||
3044                 looking_at(buf, &i, "not out of time") ||
3045                 looking_at(buf, &i, "either player is out of time") ||
3046                 looking_at(buf, &i, "has timeseal; checking")) {
3047                 /* We must have called his flag a little too soon */
3048                 whiteFlag = blackFlag = FALSE;
3049                 continue;
3050             }
3051
3052             if (looking_at(buf, &i, "added * seconds to") ||
3053                 looking_at(buf, &i, "seconds were added to")) {
3054                 /* Update the clocks */
3055                 SendToICS(ics_prefix);
3056                 SendToICS("refresh\n");
3057                 continue;
3058             }
3059
3060             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061                 ics_clock_paused = TRUE;
3062                 StopClocks();
3063                 continue;
3064             }
3065
3066             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067                 ics_clock_paused = FALSE;
3068                 StartClocks();
3069                 continue;
3070             }
3071
3072             /* Grab player ratings from the Creating: message.
3073                Note we have to check for the special case when
3074                the ICS inserts things like [white] or [black]. */
3075             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077                 /* star_matches:
3078                    0    player 1 name (not necessarily white)
3079                    1    player 1 rating
3080                    2    empty, white, or black (IGNORED)
3081                    3    player 2 name (not necessarily black)
3082                    4    player 2 rating
3083                    
3084                    The names/ratings are sorted out when the game
3085                    actually starts (below).
3086                 */
3087                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088                 player1Rating = string_to_rating(star_match[1]);
3089                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090                 player2Rating = string_to_rating(star_match[4]);
3091
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, 
3094                           "Ratings from 'Creating:' %s %d, %s %d\n",
3095                           player1Name, player1Rating,
3096                           player2Name, player2Rating);
3097
3098                 continue;
3099             }
3100             
3101             /* Improved generic start/end-of-game messages */
3102             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104                 /* If tkind == 0: */
3105                 /* star_match[0] is the game number */
3106                 /*           [1] is the white player's name */
3107                 /*           [2] is the black player's name */
3108                 /* For end-of-game: */
3109                 /*           [3] is the reason for the game end */
3110                 /*           [4] is a PGN end game-token, preceded by " " */
3111                 /* For start-of-game: */
3112                 /*           [3] begins with "Creating" or "Continuing" */
3113                 /*           [4] is " *" or empty (don't care). */
3114                 int gamenum = atoi(star_match[0]);
3115                 char *whitename, *blackname, *why, *endtoken;
3116                 ChessMove endtype = (ChessMove) 0;
3117
3118                 if (tkind == 0) {
3119                   whitename = star_match[1];
3120                   blackname = star_match[2];
3121                   why = star_match[3];
3122                   endtoken = star_match[4];
3123                 } else {
3124                   whitename = star_match[1];
3125                   blackname = star_match[3];
3126                   why = star_match[5];
3127                   endtoken = star_match[6];
3128                 }
3129
3130                 /* Game start messages */
3131                 if (strncmp(why, "Creating ", 9) == 0 ||
3132                     strncmp(why, "Continuing ", 11) == 0) {
3133                     gs_gamenum = gamenum;
3134                     strcpy(gs_kind, strchr(why, ' ') + 1);
3135 #if ZIPPY
3136                     if (appData.zippyPlay) {
3137                         ZippyGameStart(whitename, blackname);
3138                     }
3139 #endif /*ZIPPY*/
3140                     continue;
3141                 }
3142
3143                 /* Game end messages */
3144                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145                     ics_gamenum != gamenum) {
3146                     continue;
3147                 }
3148                 while (endtoken[0] == ' ') endtoken++;
3149                 switch (endtoken[0]) {
3150                   case '*':
3151                   default:
3152                     endtype = GameUnfinished;
3153                     break;
3154                   case '0':
3155                     endtype = BlackWins;
3156                     break;
3157                   case '1':
3158                     if (endtoken[1] == '/')
3159                       endtype = GameIsDrawn;
3160                     else
3161                       endtype = WhiteWins;
3162                     break;
3163                 }
3164                 GameEnds(endtype, why, GE_ICS);
3165 #if ZIPPY
3166                 if (appData.zippyPlay && first.initDone) {
3167                     ZippyGameEnd(endtype, why);
3168                     if (first.pr == NULL) {
3169                       /* Start the next process early so that we'll
3170                          be ready for the next challenge */
3171                       StartChessProgram(&first);
3172                     }
3173                     /* Send "new" early, in case this command takes
3174                        a long time to finish, so that we'll be ready
3175                        for the next challenge. */
3176                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177                     Reset(TRUE, TRUE);
3178                 }
3179 #endif /*ZIPPY*/
3180                 continue;
3181             }
3182
3183             if (looking_at(buf, &i, "Removing game * from observation") ||
3184                 looking_at(buf, &i, "no longer observing game *") ||
3185                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186                 if (gameMode == IcsObserving &&
3187                     atoi(star_match[0]) == ics_gamenum)
3188                   {
3189                       /* icsEngineAnalyze */
3190                       if (appData.icsEngineAnalyze) {
3191                             ExitAnalyzeMode();
3192                             ModeHighlight();
3193                       }
3194                       StopClocks();
3195                       gameMode = IcsIdle;
3196                       ics_gamenum = -1;
3197                       ics_user_moved = FALSE;
3198                   }
3199                 continue;
3200             }
3201
3202             if (looking_at(buf, &i, "no longer examining game *")) {
3203                 if (gameMode == IcsExamining &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             /* Advance leftover_start past any newlines we find,
3214                so only partial lines can get reparsed */
3215             if (looking_at(buf, &i, "\n")) {
3216                 prevColor = curColor;
3217                 if (curColor != ColorNormal) {
3218                     if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                     }
3222                     Colorize(ColorNormal, FALSE);
3223                     curColor = ColorNormal;
3224                 }
3225                 if (started == STARTED_BOARD) {
3226                     started = STARTED_NONE;
3227                     parse[parse_pos] = NULLCHAR;
3228                     ParseBoard12(parse);
3229                     ics_user_moved = 0;
3230
3231                     /* Send premove here */
3232                     if (appData.premove) {
3233                       char str[MSG_SIZ];
3234                       if (currentMove == 0 &&
3235                           gameMode == IcsPlayingWhite &&
3236                           appData.premoveWhite) {
3237                         sprintf(str, "%s\n", appData.premoveWhiteText);
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                         SendToICS(str);
3241                       } else if (currentMove == 1 &&
3242                                  gameMode == IcsPlayingBlack &&
3243                                  appData.premoveBlack) {
3244                         sprintf(str, "%s\n", appData.premoveBlackText);
3245                         if (appData.debugMode)
3246                           fprintf(debugFP, "Sending premove:\n");
3247                         SendToICS(str);
3248                       } else if (gotPremove) {
3249                         gotPremove = 0;
3250                         ClearPremoveHighlights();
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                           UserMoveEvent(premoveFromX, premoveFromY, 
3254                                         premoveToX, premoveToY, 
3255                                         premovePromoChar);
3256                       }
3257                     }
3258
3259                     /* Usually suppress following prompt */
3260                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261                         if (looking_at(buf, &i, "*% ")) {
3262                             savingComment = FALSE;
3263                         }
3264                     }
3265                     next_out = i;
3266                 } else if (started == STARTED_HOLDINGS) {
3267                     int gamenum;
3268                     char new_piece[MSG_SIZ];
3269                     started = STARTED_NONE;
3270                     parse[parse_pos] = NULLCHAR;
3271                     if (appData.debugMode)
3272                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3273                                                         parse, currentMove);
3274                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3275                         gamenum == ics_gamenum) {
3276                         if (gameInfo.variant == VariantNormal) {
3277                           /* [HGM] We seem to switch variant during a game!
3278                            * Presumably no holdings were displayed, so we have
3279                            * to move the position two files to the right to
3280                            * create room for them!
3281                            */
3282                           VariantClass newVariant;
3283                           switch(gameInfo.boardWidth) { // base guess on board width
3284                                 case 9:  newVariant = VariantShogi; break;
3285                                 case 10: newVariant = VariantGreat; break;
3286                                 default: newVariant = VariantCrazyhouse; break;
3287                           }
3288                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3289                           /* Get a move list just to see the header, which
3290                              will tell us whether this is really bug or zh */
3291                           if (ics_getting_history == H_FALSE) {
3292                             ics_getting_history = H_REQUESTED;
3293                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3294                             SendToICS(str);
3295                           }
3296                         }
3297                         new_piece[0] = NULLCHAR;
3298                         sscanf(parse, "game %d white [%s black [%s <- %s",
3299                                &gamenum, white_holding, black_holding,
3300                                new_piece);
3301                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3302                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3303                         /* [HGM] copy holdings to board holdings area */
3304                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3305                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3306                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3307 #if ZIPPY
3308                         if (appData.zippyPlay && first.initDone) {
3309                             ZippyHoldings(white_holding, black_holding,
3310                                           new_piece);
3311                         }
3312 #endif /*ZIPPY*/
3313                         if (tinyLayout || smallLayout) {
3314                             char wh[16], bh[16];
3315                             PackHolding(wh, white_holding);
3316                             PackHolding(bh, black_holding);
3317                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3318                                     gameInfo.white, gameInfo.black);
3319                         } else {
3320                             sprintf(str, "%s [%s] vs. %s [%s]",
3321                                     gameInfo.white, white_holding,
3322                                     gameInfo.black, black_holding);
3323                         }
3324
3325                         DrawPosition(FALSE, boards[currentMove]);
3326                         DisplayTitle(str);
3327                     }
3328                     /* Suppress following prompt */
3329                     if (looking_at(buf, &i, "*% ")) {
3330                         savingComment = FALSE;
3331                     }
3332                     next_out = i;
3333                 }
3334                 continue;
3335             }
3336
3337             i++;                /* skip unparsed character and loop back */
3338         }
3339         
3340         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3341             started != STARTED_HOLDINGS && i > next_out) {
3342             SendToPlayer(&buf[next_out], i - next_out);
3343             next_out = i;
3344         }
3345         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3346         
3347         leftover_len = buf_len - leftover_start;
3348         /* if buffer ends with something we couldn't parse,
3349            reparse it after appending the next read */
3350         
3351     } else if (count == 0) {
3352         RemoveInputSource(isr);
3353         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3354     } else {
3355         DisplayFatalError(_("Error reading from ICS"), error, 1);
3356     }
3357 }
3358
3359
3360 /* Board style 12 looks like this:
3361    
3362    <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
3363    
3364  * The "<12> " is stripped before it gets to this routine.  The two
3365  * trailing 0's (flip state and clock ticking) are later addition, and
3366  * some chess servers may not have them, or may have only the first.
3367  * Additional trailing fields may be added in the future.  
3368  */
3369
3370 #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"
3371
3372 #define RELATION_OBSERVING_PLAYED    0
3373 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3374 #define RELATION_PLAYING_MYMOVE      1
3375 #define RELATION_PLAYING_NOTMYMOVE  -1
3376 #define RELATION_EXAMINING           2
3377 #define RELATION_ISOLATED_BOARD     -3
3378 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3379
3380 void
3381 ParseBoard12(string)
3382      char *string;
3383
3384     GameMode newGameMode;
3385     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3386     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3387     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3388     char to_play, board_chars[200];
3389     char move_str[500], str[500], elapsed_time[500];
3390     char black[32], white[32];
3391     Board board;
3392     int prevMove = currentMove;
3393     int ticking = 2;
3394     ChessMove moveType;
3395     int fromX, fromY, toX, toY;
3396     char promoChar;
3397     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3398     char *bookHit = NULL; // [HGM] book
3399     Boolean weird = FALSE;
3400
3401     fromX = fromY = toX = toY = -1;
3402     
3403     newGame = FALSE;
3404
3405     if (appData.debugMode)
3406       fprintf(debugFP, _("Parsing board: %s\n"), string);
3407
3408     move_str[0] = NULLCHAR;
3409     elapsed_time[0] = NULLCHAR;
3410     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3411         int  i = 0, j;
3412         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3413             if(string[i] == ' ') { ranks++; files = 0; }
3414             else files++;
3415             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3416             i++;
3417         }
3418         for(j = 0; j <i; j++) board_chars[j] = string[j];
3419         board_chars[i] = '\0';
3420         string += i + 1;
3421     }
3422     n = sscanf(string, PATTERN, &to_play, &double_push,
3423                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3424                &gamenum, white, black, &relation, &basetime, &increment,
3425                &white_stren, &black_stren, &white_time, &black_time,
3426                &moveNum, str, elapsed_time, move_str, &ics_flip,
3427                &ticking);
3428
3429    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3430                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3431      /* [HGM] We seem to switch variant during a game!
3432       * Try to guess new variant from board size
3433       */
3434           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3435           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3436           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3437           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3438           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3439           if(!weird) newVariant = VariantNormal;
3440           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3441           /* Get a move list just to see the header, which
3442              will tell us whether this is really bug or zh */
3443           if (ics_getting_history == H_FALSE) {
3444             ics_getting_history = H_REQUESTED;
3445             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3446             SendToICS(str);
3447           }
3448     }
3449
3450     if (n < 21) {
3451         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3452         DisplayError(str, 0);
3453         return;
3454     }
3455
3456     /* Convert the move number to internal form */
3457     moveNum = (moveNum - 1) * 2;
3458     if (to_play == 'B') moveNum++;
3459     if (moveNum >= MAX_MOVES) {
3460       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3461                         0, 1);
3462       return;
3463     }
3464     
3465     switch (relation) {
3466       case RELATION_OBSERVING_PLAYED:
3467       case RELATION_OBSERVING_STATIC:
3468         if (gamenum == -1) {
3469             /* Old ICC buglet */
3470             relation = RELATION_OBSERVING_STATIC;
3471         }
3472         newGameMode = IcsObserving;
3473         break;
3474       case RELATION_PLAYING_MYMOVE:
3475       case RELATION_PLAYING_NOTMYMOVE:
3476         newGameMode =
3477           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3478             IcsPlayingWhite : IcsPlayingBlack;
3479         break;
3480       case RELATION_EXAMINING:
3481         newGameMode = IcsExamining;
3482         break;
3483       case RELATION_ISOLATED_BOARD:
3484       default:
3485         /* Just display this board.  If user was doing something else,
3486            we will forget about it until the next board comes. */ 
3487         newGameMode = IcsIdle;
3488         break;
3489       case RELATION_STARTING_POSITION:
3490         newGameMode = gameMode;
3491         break;
3492     }
3493     
3494     /* Modify behavior for initial board display on move listing
3495        of wild games.
3496        */
3497     switch (ics_getting_history) {
3498       case H_FALSE:
3499       case H_REQUESTED:
3500         break;
3501       case H_GOT_REQ_HEADER:
3502       case H_GOT_UNREQ_HEADER:
3503         /* This is the initial position of the current game */
3504         gamenum = ics_gamenum;
3505         moveNum = 0;            /* old ICS bug workaround */
3506         if (to_play == 'B') {
3507           startedFromSetupPosition = TRUE;
3508           blackPlaysFirst = TRUE;
3509           moveNum = 1;
3510           if (forwardMostMove == 0) forwardMostMove = 1;
3511           if (backwardMostMove == 0) backwardMostMove = 1;
3512           if (currentMove == 0) currentMove = 1;
3513         }
3514         newGameMode = gameMode;
3515         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3516         break;
3517       case H_GOT_UNWANTED_HEADER:
3518         /* This is an initial board that we don't want */
3519         return;
3520       case H_GETTING_MOVES:
3521         /* Should not happen */
3522         DisplayError(_("Error gathering move list: extra board"), 0);
3523         ics_getting_history = H_FALSE;
3524         return;
3525     }
3526     
3527     /* Take action if this is the first board of a new game, or of a
3528        different game than is currently being displayed.  */
3529     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3530         relation == RELATION_ISOLATED_BOARD) {
3531         
3532         /* Forget the old game and get the history (if any) of the new one */
3533         if (gameMode != BeginningOfGame) {
3534           Reset(TRUE, TRUE);
3535         }
3536         newGame = TRUE;
3537         if (appData.autoRaiseBoard) BoardToTop();
3538         prevMove = -3;
3539         if (gamenum == -1) {
3540             newGameMode = IcsIdle;
3541         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3542                    appData.getMoveList) {
3543             /* Need to get game history */
3544             ics_getting_history = H_REQUESTED;
3545             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3546             SendToICS(str);
3547         }
3548         
3549         /* Initially flip the board to have black on the bottom if playing
3550            black or if the ICS flip flag is set, but let the user change
3551            it with the Flip View button. */
3552         flipView = appData.autoFlipView ? 
3553           (newGameMode == IcsPlayingBlack) || ics_flip :
3554           appData.flipView;
3555         
3556         /* Done with values from previous mode; copy in new ones */
3557         gameMode = newGameMode;
3558         ModeHighlight();
3559         ics_gamenum = gamenum;
3560         if (gamenum == gs_gamenum) {
3561             int klen = strlen(gs_kind);
3562             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3563             sprintf(str, "ICS %s", gs_kind);
3564             gameInfo.event = StrSave(str);
3565         } else {
3566             gameInfo.event = StrSave("ICS game");
3567         }
3568         gameInfo.site = StrSave(appData.icsHost);
3569         gameInfo.date = PGNDate();
3570         gameInfo.round = StrSave("-");
3571         gameInfo.white = StrSave(white);
3572         gameInfo.black = StrSave(black);
3573         timeControl = basetime * 60 * 1000;
3574         timeControl_2 = 0;
3575         timeIncrement = increment * 1000;
3576         movesPerSession = 0;
3577         gameInfo.timeControl = TimeControlTagValue();
3578         VariantSwitch(board, StringToVariant(gameInfo.event) );
3579   if (appData.debugMode) {
3580     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3581     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3582     setbuf(debugFP, NULL);
3583   }
3584
3585         gameInfo.outOfBook = NULL;
3586         
3587         /* Do we have the ratings? */
3588         if (strcmp(player1Name, white) == 0 &&
3589             strcmp(player2Name, black) == 0) {
3590             if (appData.debugMode)
3591               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3592                       player1Rating, player2Rating);
3593             gameInfo.whiteRating = player1Rating;
3594             gameInfo.blackRating = player2Rating;
3595         } else if (strcmp(player2Name, white) == 0 &&
3596                    strcmp(player1Name, black) == 0) {
3597             if (appData.debugMode)
3598               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3599                       player2Rating, player1Rating);
3600             gameInfo.whiteRating = player2Rating;
3601             gameInfo.blackRating = player1Rating;
3602         }
3603         player1Name[0] = player2Name[0] = NULLCHAR;
3604
3605         /* Silence shouts if requested */
3606         if (appData.quietPlay &&
3607             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3608             SendToICS(ics_prefix);
3609             SendToICS("set shout 0\n");
3610         }
3611     }
3612     
3613     /* Deal with midgame name changes */
3614     if (!newGame) {
3615         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3616             if (gameInfo.white) free(gameInfo.white);
3617             gameInfo.white = StrSave(white);
3618         }
3619         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3620             if (gameInfo.black) free(gameInfo.black);
3621             gameInfo.black = StrSave(black);
3622         }
3623     }
3624     
3625     /* Throw away game result if anything actually changes in examine mode */
3626     if (gameMode == IcsExamining && !newGame) {
3627         gameInfo.result = GameUnfinished;
3628         if (gameInfo.resultDetails != NULL) {
3629             free(gameInfo.resultDetails);
3630             gameInfo.resultDetails = NULL;
3631         }
3632     }
3633     
3634     /* In pausing && IcsExamining mode, we ignore boards coming
3635        in if they are in a different variation than we are. */
3636     if (pauseExamInvalid) return;
3637     if (pausing && gameMode == IcsExamining) {
3638         if (moveNum <= pauseExamForwardMostMove) {
3639             pauseExamInvalid = TRUE;
3640             forwardMostMove = pauseExamForwardMostMove;
3641             return;
3642         }
3643     }
3644     
3645   if (appData.debugMode) {
3646     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3647   }
3648     /* Parse the board */
3649     for (k = 0; k < ranks; k++) {
3650       for (j = 0; j < files; j++)
3651         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3652       if(gameInfo.holdingsWidth > 1) {
3653            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3654            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3655       }
3656     }
3657     CopyBoard(boards[moveNum], board);
3658     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3659     if (moveNum == 0) {
3660         startedFromSetupPosition =
3661           !CompareBoards(board, initialPosition);
3662         if(startedFromSetupPosition)
3663             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3664     }
3665
3666     /* [HGM] Set castling rights. Take the outermost Rooks,
3667        to make it also work for FRC opening positions. Note that board12
3668        is really defective for later FRC positions, as it has no way to
3669        indicate which Rook can castle if they are on the same side of King.
3670        For the initial position we grant rights to the outermost Rooks,
3671        and remember thos rights, and we then copy them on positions
3672        later in an FRC game. This means WB might not recognize castlings with
3673        Rooks that have moved back to their original position as illegal,
3674        but in ICS mode that is not its job anyway.
3675     */
3676     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3677     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3678
3679         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3680             if(board[0][i] == WhiteRook) j = i;
3681         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3682         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3683             if(board[0][i] == WhiteRook) j = i;
3684         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3686             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3687         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691
3692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696             if(board[BOARD_HEIGHT-1][k] == bKing)
3697                 initialRights[5] = castlingRights[moveNum][5] = k;
3698     } else { int r;
3699         r = castlingRights[moveNum][0] = initialRights[0];
3700         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3701         r = castlingRights[moveNum][1] = initialRights[1];
3702         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3703         r = castlingRights[moveNum][3] = initialRights[3];
3704         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3705         r = castlingRights[moveNum][4] = initialRights[4];
3706         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3707         /* wildcastle kludge: always assume King has rights */
3708         r = castlingRights[moveNum][2] = initialRights[2];
3709         r = castlingRights[moveNum][5] = initialRights[5];
3710     }
3711     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3712     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3713
3714     
3715     if (ics_getting_history == H_GOT_REQ_HEADER ||
3716         ics_getting_history == H_GOT_UNREQ_HEADER) {
3717         /* This was an initial position from a move list, not
3718            the current position */
3719         return;
3720     }
3721     
3722     /* Update currentMove and known move number limits */
3723     newMove = newGame || moveNum > forwardMostMove;
3724
3725     if (newGame) {
3726         forwardMostMove = backwardMostMove = currentMove = moveNum;
3727         if (gameMode == IcsExamining && moveNum == 0) {
3728           /* Workaround for ICS limitation: we are not told the wild
3729              type when starting to examine a game.  But if we ask for
3730              the move list, the move list header will tell us */
3731             ics_getting_history = H_REQUESTED;
3732             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3733             SendToICS(str);
3734         }
3735     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3736                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3737 #if ZIPPY
3738         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3739         /* [HGM] applied this also to an engine that is silently watching        */
3740         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3741             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3742             gameInfo.variant == currentlyInitializedVariant) {
3743           takeback = forwardMostMove - moveNum;
3744           for (i = 0; i < takeback; i++) {
3745             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3746             SendToProgram("undo\n", &first);
3747           }
3748         }
3749 #endif
3750
3751         forwardMostMove = moveNum;
3752         if (!pausing || currentMove > forwardMostMove)
3753           currentMove = forwardMostMove;
3754     } else {
3755         /* New part of history that is not contiguous with old part */ 
3756         if (pausing && gameMode == IcsExamining) {
3757             pauseExamInvalid = TRUE;
3758             forwardMostMove = pauseExamForwardMostMove;
3759             return;
3760         }
3761         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3762 #if ZIPPY
3763             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3764                 // [HGM] when we will receive the move list we now request, it will be
3765                 // fed to the engine from the first move on. So if the engine is not
3766                 // in the initial position now, bring it there.
3767                 InitChessProgram(&first, 0);
3768             }
3769 #endif
3770             ics_getting_history = H_REQUESTED;
3771             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3772             SendToICS(str);
3773         }
3774         forwardMostMove = backwardMostMove = currentMove = moveNum;
3775     }
3776     
3777     /* Update the clocks */
3778     if (strchr(elapsed_time, '.')) {
3779       /* Time is in ms */
3780       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3781       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3782     } else {
3783       /* Time is in seconds */
3784       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3785       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3786     }
3787       
3788
3789 #if ZIPPY
3790     if (appData.zippyPlay && newGame &&
3791         gameMode != IcsObserving && gameMode != IcsIdle &&
3792         gameMode != IcsExamining)
3793       ZippyFirstBoard(moveNum, basetime, increment);
3794 #endif
3795     
3796     /* Put the move on the move list, first converting
3797        to canonical algebraic form. */
3798     if (moveNum > 0) {
3799   if (appData.debugMode) {
3800     if (appData.debugMode) { int f = forwardMostMove;
3801         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3802                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3803     }
3804     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3805     fprintf(debugFP, "moveNum = %d\n", moveNum);
3806     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3807     setbuf(debugFP, NULL);
3808   }
3809         if (moveNum <= backwardMostMove) {
3810             /* We don't know what the board looked like before
3811                this move.  Punt. */
3812             strcpy(parseList[moveNum - 1], move_str);
3813             strcat(parseList[moveNum - 1], " ");
3814             strcat(parseList[moveNum - 1], elapsed_time);
3815             moveList[moveNum - 1][0] = NULLCHAR;
3816         } else if (strcmp(move_str, "none") == 0) {
3817             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3818             /* Again, we don't know what the board looked like;
3819                this is really the start of the game. */
3820             parseList[moveNum - 1][0] = NULLCHAR;
3821             moveList[moveNum - 1][0] = NULLCHAR;
3822             backwardMostMove = moveNum;
3823             startedFromSetupPosition = TRUE;
3824             fromX = fromY = toX = toY = -1;
3825         } else {
3826           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3827           //                 So we parse the long-algebraic move string in stead of the SAN move
3828           int valid; char buf[MSG_SIZ], *prom;
3829
3830           // str looks something like "Q/a1-a2"; kill the slash
3831           if(str[1] == '/') 
3832                 sprintf(buf, "%c%s", str[0], str+2);
3833           else  strcpy(buf, str); // might be castling
3834           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3835                 strcat(buf, prom); // long move lacks promo specification!
3836           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3837                 if(appData.debugMode) 
3838                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3839                 strcpy(move_str, buf);
3840           }
3841           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3842                                 &fromX, &fromY, &toX, &toY, &promoChar)
3843                || ParseOneMove(buf, moveNum - 1, &moveType,
3844                                 &fromX, &fromY, &toX, &toY, &promoChar);
3845           // end of long SAN patch
3846           if (valid) {
3847             (void) CoordsToAlgebraic(boards[moveNum - 1],
3848                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3849                                      fromY, fromX, toY, toX, promoChar,
3850                                      parseList[moveNum-1]);
3851             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3852                              castlingRights[moveNum]) ) {
3853               case MT_NONE:
3854               case MT_STALEMATE:
3855               default:
3856                 break;
3857               case MT_CHECK:
3858                 if(gameInfo.variant != VariantShogi)
3859                     strcat(parseList[moveNum - 1], "+");
3860                 break;
3861               case MT_CHECKMATE:
3862               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3863                 strcat(parseList[moveNum - 1], "#");
3864                 break;
3865             }
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             /* currentMoveString is set as a side-effect of ParseOneMove */
3869             strcpy(moveList[moveNum - 1], currentMoveString);
3870             strcat(moveList[moveNum - 1], "\n");
3871           } else {
3872             /* Move from ICS was illegal!?  Punt. */
3873   if (appData.debugMode) {
3874     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3875     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3876   }
3877             strcpy(parseList[moveNum - 1], move_str);
3878             strcat(parseList[moveNum - 1], " ");
3879             strcat(parseList[moveNum - 1], elapsed_time);
3880             moveList[moveNum - 1][0] = NULLCHAR;
3881             fromX = fromY = toX = toY = -1;
3882           }
3883         }
3884   if (appData.debugMode) {
3885     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3886     setbuf(debugFP, NULL);
3887   }
3888
3889 #if ZIPPY
3890         /* Send move to chess program (BEFORE animating it). */
3891         if (appData.zippyPlay && !newGame && newMove && 
3892            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3893
3894             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3895                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3896                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3897                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3898                             move_str);
3899                     DisplayError(str, 0);
3900                 } else {
3901                     if (first.sendTime) {
3902                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3903                     }
3904                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3905                     if (firstMove && !bookHit) {
3906                         firstMove = FALSE;
3907                         if (first.useColors) {
3908                           SendToProgram(gameMode == IcsPlayingWhite ?
3909                                         "white\ngo\n" :
3910                                         "black\ngo\n", &first);
3911                         } else {
3912                           SendToProgram("go\n", &first);
3913                         }
3914                         first.maybeThinking = TRUE;
3915                     }
3916                 }
3917             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3918               if (moveList[moveNum - 1][0] == NULLCHAR) {
3919                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3920                 DisplayError(str, 0);
3921               } else {
3922                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3923                 SendMoveToProgram(moveNum - 1, &first);
3924               }
3925             }
3926         }
3927 #endif
3928     }
3929
3930     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3931         /* If move comes from a remote source, animate it.  If it
3932            isn't remote, it will have already been animated. */
3933         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3934             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3935         }
3936         if (!pausing && appData.highlightLastMove) {
3937             SetHighlights(fromX, fromY, toX, toY);
3938         }
3939     }
3940     
3941     /* Start the clocks */
3942     whiteFlag = blackFlag = FALSE;
3943     appData.clockMode = !(basetime == 0 && increment == 0);
3944     if (ticking == 0) {
3945       ics_clock_paused = TRUE;
3946       StopClocks();
3947     } else if (ticking == 1) {
3948       ics_clock_paused = FALSE;
3949     }
3950     if (gameMode == IcsIdle ||
3951         relation == RELATION_OBSERVING_STATIC ||
3952         relation == RELATION_EXAMINING ||
3953         ics_clock_paused)
3954       DisplayBothClocks();
3955     else
3956       StartClocks();
3957     
3958     /* Display opponents and material strengths */
3959     if (gameInfo.variant != VariantBughouse &&
3960         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3961         if (tinyLayout || smallLayout) {
3962             if(gameInfo.variant == VariantNormal)
3963                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3964                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3965                     basetime, increment);
3966             else
3967                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3968                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3969                     basetime, increment, (int) gameInfo.variant);
3970         } else {
3971             if(gameInfo.variant == VariantNormal)
3972                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3974                     basetime, increment);
3975             else
3976                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3977                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3978                     basetime, increment, VariantName(gameInfo.variant));
3979         }
3980         DisplayTitle(str);
3981   if (appData.debugMode) {
3982     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3983   }
3984     }
3985
3986    
3987     /* Display the board */
3988     if (!pausing && !appData.noGUI) {
3989       
3990       if (appData.premove)
3991           if (!gotPremove || 
3992              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3993              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3994               ClearPremoveHighlights();
3995
3996       DrawPosition(FALSE, boards[currentMove]);
3997       DisplayMove(moveNum - 1);
3998       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3999             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4000               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4001         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4002       }
4003     }
4004
4005     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4006 #if ZIPPY
4007     if(bookHit) { // [HGM] book: simulate book reply
4008         static char bookMove[MSG_SIZ]; // a bit generous?
4009
4010         programStats.nodes = programStats.depth = programStats.time = 
4011         programStats.score = programStats.got_only_move = 0;
4012         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4013
4014         strcpy(bookMove, "move ");
4015         strcat(bookMove, bookHit);
4016         HandleMachineMove(bookMove, &first);
4017     }
4018 #endif
4019 }
4020
4021 void
4022 GetMoveListEvent()
4023 {
4024     char buf[MSG_SIZ];
4025     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4026         ics_getting_history = H_REQUESTED;
4027         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4028         SendToICS(buf);
4029     }
4030 }
4031
4032 void
4033 AnalysisPeriodicEvent(force)
4034      int force;
4035 {
4036     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4037          && !force) || !appData.periodicUpdates)
4038       return;
4039
4040     /* Send . command to Crafty to collect stats */
4041     SendToProgram(".\n", &first);
4042
4043     /* Don't send another until we get a response (this makes
4044        us stop sending to old Crafty's which don't understand
4045        the "." command (sending illegal cmds resets node count & time,
4046        which looks bad)) */
4047     programStats.ok_to_send = 0;
4048 }
4049
4050 void ics_update_width(new_width)
4051         int new_width;
4052 {
4053         ics_printf("set width %d\n", new_width);
4054 }
4055
4056 void
4057 SendMoveToProgram(moveNum, cps)
4058      int moveNum;
4059      ChessProgramState *cps;
4060 {
4061     char buf[MSG_SIZ];
4062
4063     if (cps->useUsermove) {
4064       SendToProgram("usermove ", cps);
4065     }
4066     if (cps->useSAN) {
4067       char *space;
4068       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4069         int len = space - parseList[moveNum];
4070         memcpy(buf, parseList[moveNum], len);
4071         buf[len++] = '\n';
4072         buf[len] = NULLCHAR;
4073       } else {
4074         sprintf(buf, "%s\n", parseList[moveNum]);
4075       }
4076       SendToProgram(buf, cps);
4077     } else {
4078       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4079         AlphaRank(moveList[moveNum], 4);
4080         SendToProgram(moveList[moveNum], cps);
4081         AlphaRank(moveList[moveNum], 4); // and back
4082       } else
4083       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4084        * the engine. It would be nice to have a better way to identify castle 
4085        * moves here. */
4086       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4087                                                                          && cps->useOOCastle) {
4088         int fromX = moveList[moveNum][0] - AAA; 
4089         int fromY = moveList[moveNum][1] - ONE;
4090         int toX = moveList[moveNum][2] - AAA; 
4091         int toY = moveList[moveNum][3] - ONE;
4092         if((boards[moveNum][fromY][fromX] == WhiteKing 
4093             && boards[moveNum][toY][toX] == WhiteRook)
4094            || (boards[moveNum][fromY][fromX] == BlackKing 
4095                && boards[moveNum][toY][toX] == BlackRook)) {
4096           if(toX > fromX) SendToProgram("O-O\n", cps);
4097           else SendToProgram("O-O-O\n", cps);
4098         }
4099         else SendToProgram(moveList[moveNum], cps);
4100       }
4101       else SendToProgram(moveList[moveNum], cps);
4102       /* End of additions by Tord */
4103     }
4104
4105     /* [HGM] setting up the opening has brought engine in force mode! */
4106     /*       Send 'go' if we are in a mode where machine should play. */
4107     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4108         (gameMode == TwoMachinesPlay   ||
4109 #ifdef ZIPPY
4110          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4111 #endif
4112          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4113         SendToProgram("go\n", cps);
4114   if (appData.debugMode) {
4115     fprintf(debugFP, "(extra)\n");
4116   }
4117     }
4118     setboardSpoiledMachineBlack = 0;
4119 }
4120
4121 void
4122 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4123      ChessMove moveType;
4124      int fromX, fromY, toX, toY;
4125 {
4126     char user_move[MSG_SIZ];
4127
4128     switch (moveType) {
4129       default:
4130         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4131                 (int)moveType, fromX, fromY, toX, toY);
4132         DisplayError(user_move + strlen("say "), 0);
4133         break;
4134       case WhiteKingSideCastle:
4135       case BlackKingSideCastle:
4136       case WhiteQueenSideCastleWild:
4137       case BlackQueenSideCastleWild:
4138       /* PUSH Fabien */
4139       case WhiteHSideCastleFR:
4140       case BlackHSideCastleFR:
4141       /* POP Fabien */
4142         sprintf(user_move, "o-o\n");
4143         break;
4144       case WhiteQueenSideCastle:
4145       case BlackQueenSideCastle:
4146       case WhiteKingSideCastleWild:
4147       case BlackKingSideCastleWild:
4148       /* PUSH Fabien */
4149       case WhiteASideCastleFR:
4150       case BlackASideCastleFR:
4151       /* POP Fabien */
4152         sprintf(user_move, "o-o-o\n");
4153         break;
4154       case WhitePromotionQueen:
4155       case BlackPromotionQueen:
4156       case WhitePromotionRook:
4157       case BlackPromotionRook:
4158       case WhitePromotionBishop:
4159       case BlackPromotionBishop:
4160       case WhitePromotionKnight:
4161       case BlackPromotionKnight:
4162       case WhitePromotionKing:
4163       case BlackPromotionKing:
4164       case WhitePromotionChancellor:
4165       case BlackPromotionChancellor:
4166       case WhitePromotionArchbishop:
4167       case BlackPromotionArchbishop:
4168         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4169             sprintf(user_move, "%c%c%c%c=%c\n",
4170                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4171                 PieceToChar(WhiteFerz));
4172         else if(gameInfo.variant == VariantGreat)
4173             sprintf(user_move, "%c%c%c%c=%c\n",
4174                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4175                 PieceToChar(WhiteMan));
4176         else
4177             sprintf(user_move, "%c%c%c%c=%c\n",
4178                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4179                 PieceToChar(PromoPiece(moveType)));
4180         break;
4181       case WhiteDrop:
4182       case BlackDrop:
4183         sprintf(user_move, "%c@%c%c\n",
4184                 ToUpper(PieceToChar((ChessSquare) fromX)),
4185                 AAA + toX, ONE + toY);
4186         break;
4187       case NormalMove:
4188       case WhiteCapturesEnPassant:
4189       case BlackCapturesEnPassant:
4190       case IllegalMove:  /* could be a variant we don't quite understand */
4191         sprintf(user_move, "%c%c%c%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4193         break;
4194     }
4195     SendToICS(user_move);
4196     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4197         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4198 }
4199
4200 void
4201 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4202      int rf, ff, rt, ft;
4203      char promoChar;
4204      char move[7];
4205 {
4206     if (rf == DROP_RANK) {
4207         sprintf(move, "%c@%c%c\n",
4208                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4209     } else {
4210         if (promoChar == 'x' || promoChar == NULLCHAR) {
4211             sprintf(move, "%c%c%c%c\n",
4212                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4213         } else {
4214             sprintf(move, "%c%c%c%c%c\n",
4215                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4216         }
4217     }
4218 }
4219
4220 void
4221 ProcessICSInitScript(f)
4222      FILE *f;
4223 {
4224     char buf[MSG_SIZ];
4225
4226     while (fgets(buf, MSG_SIZ, f)) {
4227         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4228     }
4229
4230     fclose(f);
4231 }
4232
4233
4234 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4235 void
4236 AlphaRank(char *move, int n)
4237 {
4238 //    char *p = move, c; int x, y;
4239
4240     if (appData.debugMode) {
4241         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4242     }
4243
4244     if(move[1]=='*' && 
4245        move[2]>='0' && move[2]<='9' &&
4246        move[3]>='a' && move[3]<='x'    ) {
4247         move[1] = '@';
4248         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4249         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4250     } else
4251     if(move[0]>='0' && move[0]<='9' &&
4252        move[1]>='a' && move[1]<='x' &&
4253        move[2]>='0' && move[2]<='9' &&
4254        move[3]>='a' && move[3]<='x'    ) {
4255         /* input move, Shogi -> normal */
4256         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4257         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4258         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4259         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4260     } else
4261     if(move[1]=='@' &&
4262        move[3]>='0' && move[3]<='9' &&
4263        move[2]>='a' && move[2]<='x'    ) {
4264         move[1] = '*';
4265         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4266         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4267     } else
4268     if(
4269        move[0]>='a' && move[0]<='x' &&
4270        move[3]>='0' && move[3]<='9' &&
4271        move[2]>='a' && move[2]<='x'    ) {
4272          /* output move, normal -> Shogi */
4273         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4274         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4275         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4276         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4277         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4278     }
4279     if (appData.debugMode) {
4280         fprintf(debugFP, "   out = '%s'\n", move);
4281     }
4282 }
4283
4284 /* Parser for moves from gnuchess, ICS, or user typein box */
4285 Boolean
4286 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4287      char *move;
4288      int moveNum;
4289      ChessMove *moveType;
4290      int *fromX, *fromY, *toX, *toY;
4291      char *promoChar;
4292 {       
4293     if (appData.debugMode) {
4294         fprintf(debugFP, "move to parse: %s\n", move);
4295     }
4296     *moveType = yylexstr(moveNum, move);
4297
4298     switch (*moveType) {
4299       case WhitePromotionChancellor:
4300       case BlackPromotionChancellor:
4301       case WhitePromotionArchbishop:
4302       case BlackPromotionArchbishop:
4303       case WhitePromotionQueen:
4304       case BlackPromotionQueen:
4305       case WhitePromotionRook:
4306       case BlackPromotionRook:
4307       case WhitePromotionBishop:
4308       case BlackPromotionBishop:
4309       case WhitePromotionKnight:
4310       case BlackPromotionKnight:
4311       case WhitePromotionKing:
4312       case BlackPromotionKing:
4313       case NormalMove:
4314       case WhiteCapturesEnPassant:
4315       case BlackCapturesEnPassant:
4316       case WhiteKingSideCastle:
4317       case WhiteQueenSideCastle:
4318       case BlackKingSideCastle:
4319       case BlackQueenSideCastle:
4320       case WhiteKingSideCastleWild:
4321       case WhiteQueenSideCastleWild:
4322       case BlackKingSideCastleWild:
4323       case BlackQueenSideCastleWild:
4324       /* Code added by Tord: */
4325       case WhiteHSideCastleFR:
4326       case WhiteASideCastleFR:
4327       case BlackHSideCastleFR:
4328       case BlackASideCastleFR:
4329       /* End of code added by Tord */
4330       case IllegalMove:         /* bug or odd chess variant */
4331         *fromX = currentMoveString[0] - AAA;
4332         *fromY = currentMoveString[1] - ONE;
4333         *toX = currentMoveString[2] - AAA;
4334         *toY = currentMoveString[3] - ONE;
4335         *promoChar = currentMoveString[4];
4336         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4337             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4338     if (appData.debugMode) {
4339         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4340     }
4341             *fromX = *fromY = *toX = *toY = 0;
4342             return FALSE;
4343         }
4344         if (appData.testLegality) {
4345           return (*moveType != IllegalMove);
4346         } else {
4347           return !(fromX == fromY && toX == toY);
4348         }
4349
4350       case WhiteDrop:
4351       case BlackDrop:
4352         *fromX = *moveType == WhiteDrop ?
4353           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4354           (int) CharToPiece(ToLower(currentMoveString[0]));
4355         *fromY = DROP_RANK;
4356         *toX = currentMoveString[2] - AAA;
4357         *toY = currentMoveString[3] - ONE;
4358         *promoChar = NULLCHAR;
4359         return TRUE;
4360
4361       case AmbiguousMove:
4362       case ImpossibleMove:
4363       case (ChessMove) 0:       /* end of file */
4364       case ElapsedTime:
4365       case Comment:
4366       case PGNTag:
4367       case NAG:
4368       case WhiteWins:
4369       case BlackWins:
4370       case GameIsDrawn:
4371       default:
4372     if (appData.debugMode) {
4373         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4374     }
4375         /* bug? */
4376         *fromX = *fromY = *toX = *toY = 0;
4377         *promoChar = NULLCHAR;
4378         return FALSE;
4379     }
4380 }
4381
4382 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4383 // All positions will have equal probability, but the current method will not provide a unique
4384 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4385 #define DARK 1
4386 #define LITE 2
4387 #define ANY 3
4388
4389 int squaresLeft[4];
4390 int piecesLeft[(int)BlackPawn];
4391 int seed, nrOfShuffles;
4392
4393 void GetPositionNumber()
4394 {       // sets global variable seed
4395         int i;
4396
4397         seed = appData.defaultFrcPosition;
4398         if(seed < 0) { // randomize based on time for negative FRC position numbers
4399                 for(i=0; i<50; i++) seed += random();
4400                 seed = random() ^ random() >> 8 ^ random() << 8;
4401                 if(seed<0) seed = -seed;
4402         }
4403 }
4404
4405 int put(Board board, int pieceType, int rank, int n, int shade)
4406 // put the piece on the (n-1)-th empty squares of the given shade
4407 {
4408         int i;
4409
4410         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4411                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4412                         board[rank][i] = (ChessSquare) pieceType;
4413                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4414                         squaresLeft[ANY]--;
4415                         piecesLeft[pieceType]--; 
4416                         return i;
4417                 }
4418         }
4419         return -1;
4420 }
4421
4422
4423 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4424 // calculate where the next piece goes, (any empty square), and put it there
4425 {
4426         int i;
4427
4428         i = seed % squaresLeft[shade];
4429         nrOfShuffles *= squaresLeft[shade];
4430         seed /= squaresLeft[shade];
4431         put(board, pieceType, rank, i, shade);
4432 }
4433
4434 void AddTwoPieces(Board board, int pieceType, int rank)
4435 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4436 {
4437         int i, n=squaresLeft[ANY], j=n-1, k;
4438
4439         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4440         i = seed % k;  // pick one
4441         nrOfShuffles *= k;
4442         seed /= k;
4443         while(i >= j) i -= j--;
4444         j = n - 1 - j; i += j;
4445         put(board, pieceType, rank, j, ANY);
4446         put(board, pieceType, rank, i, ANY);
4447 }
4448
4449 void SetUpShuffle(Board board, int number)
4450 {
4451         int i, p, first=1;
4452
4453         GetPositionNumber(); nrOfShuffles = 1;
4454
4455         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4456         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4457         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4458
4459         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4460
4461         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4462             p = (int) board[0][i];
4463             if(p < (int) BlackPawn) piecesLeft[p] ++;
4464             board[0][i] = EmptySquare;
4465         }
4466
4467         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4468             // shuffles restricted to allow normal castling put KRR first
4469             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4470                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4471             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4472                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4473             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4474                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4475             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4476                 put(board, WhiteRook, 0, 0, ANY);
4477             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4478         }
4479
4480         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4481             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4482             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4483                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4484                 while(piecesLeft[p] >= 2) {
4485                     AddOnePiece(board, p, 0, LITE);
4486                     AddOnePiece(board, p, 0, DARK);
4487                 }
4488                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4489             }
4490
4491         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4492             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4493             // but we leave King and Rooks for last, to possibly obey FRC restriction
4494             if(p == (int)WhiteRook) continue;
4495             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4496             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4497         }
4498
4499         // now everything is placed, except perhaps King (Unicorn) and Rooks
4500
4501         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4502             // Last King gets castling rights
4503             while(piecesLeft[(int)WhiteUnicorn]) {
4504                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4505                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4506             }
4507
4508             while(piecesLeft[(int)WhiteKing]) {
4509                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4511             }
4512
4513
4514         } else {
4515             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4516             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4517         }
4518
4519         // Only Rooks can be left; simply place them all
4520         while(piecesLeft[(int)WhiteRook]) {
4521                 i = put(board, WhiteRook, 0, 0, ANY);
4522                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4523                         if(first) {
4524                                 first=0;
4525                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4526                         }
4527                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4528                 }
4529         }
4530         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4531             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4532         }
4533
4534         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4535 }
4536
4537 int SetCharTable( char *table, const char * map )
4538 /* [HGM] moved here from winboard.c because of its general usefulness */
4539 /*       Basically a safe strcpy that uses the last character as King */
4540 {
4541     int result = FALSE; int NrPieces;
4542
4543     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4544                     && NrPieces >= 12 && !(NrPieces&1)) {
4545         int i; /* [HGM] Accept even length from 12 to 34 */
4546
4547         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4548         for( i=0; i<NrPieces/2-1; i++ ) {
4549             table[i] = map[i];
4550             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4551         }
4552         table[(int) WhiteKing]  = map[NrPieces/2-1];
4553         table[(int) BlackKing]  = map[NrPieces-1];
4554
4555         result = TRUE;
4556     }
4557
4558     return result;
4559 }
4560
4561 void Prelude(Board board)
4562 {       // [HGM] superchess: random selection of exo-pieces
4563         int i, j, k; ChessSquare p; 
4564         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4565
4566         GetPositionNumber(); // use FRC position number
4567
4568         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4569             SetCharTable(pieceToChar, appData.pieceToCharTable);
4570             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4571                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4572         }
4573
4574         j = seed%4;                 seed /= 4; 
4575         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4576         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4577         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4578         j = seed%3 + (seed%3 >= j); seed /= 3; 
4579         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4580         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4581         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4582         j = seed%3;                 seed /= 3; 
4583         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4584         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4585         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4586         j = seed%2 + (seed%2 >= j); seed /= 2; 
4587         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4588         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4589         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4590         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4591         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4592         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4593         put(board, exoPieces[0],    0, 0, ANY);
4594         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4595 }
4596
4597 void
4598 InitPosition(redraw)
4599      int redraw;
4600 {
4601     ChessSquare (* pieces)[BOARD_SIZE];
4602     int i, j, pawnRow, overrule,
4603     oldx = gameInfo.boardWidth,
4604     oldy = gameInfo.boardHeight,
4605     oldh = gameInfo.holdingsWidth,
4606     oldv = gameInfo.variant;
4607
4608     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4609
4610     /* [AS] Initialize pv info list [HGM] and game status */
4611     {
4612         for( i=0; i<MAX_MOVES; i++ ) {
4613             pvInfoList[i].depth = 0;
4614             epStatus[i]=EP_NONE;
4615             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4616         }
4617
4618         initialRulePlies = 0; /* 50-move counter start */
4619
4620         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4621         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4622     }
4623
4624     
4625     /* [HGM] logic here is completely changed. In stead of full positions */
4626     /* the initialized data only consist of the two backranks. The switch */
4627     /* selects which one we will use, which is than copied to the Board   */
4628     /* initialPosition, which for the rest is initialized by Pawns and    */
4629     /* empty squares. This initial position is then copied to boards[0],  */
4630     /* possibly after shuffling, so that it remains available.            */
4631
4632     gameInfo.holdingsWidth = 0; /* default board sizes */
4633     gameInfo.boardWidth    = 8;
4634     gameInfo.boardHeight   = 8;
4635     gameInfo.holdingsSize  = 0;
4636     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4637     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4638     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4639
4640     switch (gameInfo.variant) {
4641     case VariantFischeRandom:
4642       shuffleOpenings = TRUE;
4643     default:
4644       pieces = FIDEArray;
4645       break;
4646     case VariantShatranj:
4647       pieces = ShatranjArray;
4648       nrCastlingRights = 0;
4649       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4650       break;
4651     case VariantTwoKings:
4652       pieces = twoKingsArray;
4653       break;
4654     case VariantCapaRandom:
4655       shuffleOpenings = TRUE;
4656     case VariantCapablanca:
4657       pieces = CapablancaArray;
4658       gameInfo.boardWidth = 10;
4659       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4660       break;
4661     case VariantGothic:
4662       pieces = GothicArray;
4663       gameInfo.boardWidth = 10;
4664       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4665       break;
4666     case VariantJanus:
4667       pieces = JanusArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4670       nrCastlingRights = 6;
4671         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4672         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4673         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4674         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4675         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4676         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4677       break;
4678     case VariantFalcon:
4679       pieces = FalconArray;
4680       gameInfo.boardWidth = 10;
4681       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4682       break;
4683     case VariantXiangqi:
4684       pieces = XiangqiArray;
4685       gameInfo.boardWidth  = 9;
4686       gameInfo.boardHeight = 10;
4687       nrCastlingRights = 0;
4688       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4689       break;
4690     case VariantShogi:
4691       pieces = ShogiArray;
4692       gameInfo.boardWidth  = 9;
4693       gameInfo.boardHeight = 9;
4694       gameInfo.holdingsSize = 7;
4695       nrCastlingRights = 0;
4696       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4697       break;
4698     case VariantCourier:
4699       pieces = CourierArray;
4700       gameInfo.boardWidth  = 12;
4701       nrCastlingRights = 0;
4702       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4703       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4704       break;
4705     case VariantKnightmate:
4706       pieces = KnightmateArray;
4707       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4708       break;
4709     case VariantFairy:
4710       pieces = fairyArray;
4711       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4712       break;
4713     case VariantGreat:
4714       pieces = GreatArray;
4715       gameInfo.boardWidth = 10;
4716       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4717       gameInfo.holdingsSize = 8;
4718       break;
4719     case VariantSuper:
4720       pieces = FIDEArray;
4721       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4722       gameInfo.holdingsSize = 8;
4723       startedFromSetupPosition = TRUE;
4724       break;
4725     case VariantCrazyhouse:
4726     case VariantBughouse:
4727       pieces = FIDEArray;
4728       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4729       gameInfo.holdingsSize = 5;
4730       break;
4731     case VariantWildCastle:
4732       pieces = FIDEArray;
4733       /* !!?shuffle with kings guaranteed to be on d or e file */
4734       shuffleOpenings = 1;
4735       break;
4736     case VariantNoCastle:
4737       pieces = FIDEArray;
4738       nrCastlingRights = 0;
4739       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4740       /* !!?unconstrained back-rank shuffle */
4741       shuffleOpenings = 1;
4742       break;
4743     }
4744
4745     overrule = 0;
4746     if(appData.NrFiles >= 0) {
4747         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4748         gameInfo.boardWidth = appData.NrFiles;
4749     }
4750     if(appData.NrRanks >= 0) {
4751         gameInfo.boardHeight = appData.NrRanks;
4752     }
4753     if(appData.holdingsSize >= 0) {
4754         i = appData.holdingsSize;
4755         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4756         gameInfo.holdingsSize = i;
4757     }
4758     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4759     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4760         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4761
4762     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4763     if(pawnRow < 1) pawnRow = 1;
4764
4765     /* User pieceToChar list overrules defaults */
4766     if(appData.pieceToCharTable != NULL)
4767         SetCharTable(pieceToChar, appData.pieceToCharTable);
4768
4769     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4770
4771         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4772             s = (ChessSquare) 0; /* account holding counts in guard band */
4773         for( i=0; i<BOARD_HEIGHT; i++ )
4774             initialPosition[i][j] = s;
4775
4776         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4777         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4778         initialPosition[pawnRow][j] = WhitePawn;
4779         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4780         if(gameInfo.variant == VariantXiangqi) {
4781             if(j&1) {
4782                 initialPosition[pawnRow][j] = 
4783                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4784                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4785                    initialPosition[2][j] = WhiteCannon;
4786                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4787                 }
4788             }
4789         }
4790         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4791     }
4792     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4793
4794             j=BOARD_LEFT+1;
4795             initialPosition[1][j] = WhiteBishop;
4796             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4797             j=BOARD_RGHT-2;
4798             initialPosition[1][j] = WhiteRook;
4799             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4800     }
4801
4802     if( nrCastlingRights == -1) {
4803         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4804         /*       This sets default castling rights from none to normal corners   */
4805         /* Variants with other castling rights must set them themselves above    */
4806         nrCastlingRights = 6;
4807        
4808         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4809         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4810         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4811         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4812         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4813         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4814      }
4815
4816      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4817      if(gameInfo.variant == VariantGreat) { // promotion commoners
4818         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4819         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4820         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4821         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4822      }
4823   if (appData.debugMode) {
4824     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4825   }
4826     if(shuffleOpenings) {
4827         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4828         startedFromSetupPosition = TRUE;
4829     }
4830     if(startedFromPositionFile) {
4831       /* [HGM] loadPos: use PositionFile for every new game */
4832       CopyBoard(initialPosition, filePosition);
4833       for(i=0; i<nrCastlingRights; i++)
4834           castlingRights[0][i] = initialRights[i] = fileRights[i];
4835       startedFromSetupPosition = TRUE;
4836     }
4837
4838     CopyBoard(boards[0], initialPosition);
4839
4840     if(oldx != gameInfo.boardWidth ||
4841        oldy != gameInfo.boardHeight ||
4842        oldh != gameInfo.holdingsWidth
4843 #ifdef GOTHIC
4844        || oldv == VariantGothic ||        // For licensing popups
4845        gameInfo.variant == VariantGothic
4846 #endif
4847 #ifdef FALCON
4848        || oldv == VariantFalcon ||
4849        gameInfo.variant == VariantFalcon
4850 #endif
4851                                          )
4852             InitDrawingSizes(-2 ,0);
4853
4854     if (redraw)
4855       DrawPosition(TRUE, boards[currentMove]);
4856 }
4857
4858 void
4859 SendBoard(cps, moveNum)
4860      ChessProgramState *cps;
4861      int moveNum;
4862 {
4863     char message[MSG_SIZ];
4864     
4865     if (cps->useSetboard) {
4866       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4867       sprintf(message, "setboard %s\n", fen);
4868       SendToProgram(message, cps);
4869       free(fen);
4870
4871     } else {
4872       ChessSquare *bp;
4873       int i, j;
4874       /* Kludge to set black to move, avoiding the troublesome and now
4875        * deprecated "black" command.
4876        */
4877       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4878
4879       SendToProgram("edit\n", cps);
4880       SendToProgram("#\n", cps);
4881       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4882         bp = &boards[moveNum][i][BOARD_LEFT];
4883         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4884           if ((int) *bp < (int) BlackPawn) {
4885             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4886                     AAA + j, ONE + i);
4887             if(message[0] == '+' || message[0] == '~') {
4888                 sprintf(message, "%c%c%c+\n",
4889                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4890                         AAA + j, ONE + i);
4891             }
4892             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4893                 message[1] = BOARD_RGHT   - 1 - j + '1';
4894                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4895             }
4896             SendToProgram(message, cps);
4897           }
4898         }
4899       }
4900     
4901       SendToProgram("c\n", cps);
4902       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4903         bp = &boards[moveNum][i][BOARD_LEFT];
4904         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4905           if (((int) *bp != (int) EmptySquare)
4906               && ((int) *bp >= (int) BlackPawn)) {
4907             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4908                     AAA + j, ONE + i);
4909             if(message[0] == '+' || message[0] == '~') {
4910                 sprintf(message, "%c%c%c+\n",
4911                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4912                         AAA + j, ONE + i);
4913             }
4914             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4915                 message[1] = BOARD_RGHT   - 1 - j + '1';
4916                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4917             }
4918             SendToProgram(message, cps);
4919           }
4920         }
4921       }
4922     
4923       SendToProgram(".\n", cps);
4924     }
4925     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4926 }
4927
4928 int
4929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4930 {
4931     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4932     /* [HGM] add Shogi promotions */
4933     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4934     ChessSquare piece;
4935     ChessMove moveType;
4936     Boolean premove;
4937
4938     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4939     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4940
4941     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4942       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4943         return FALSE;
4944
4945     piece = boards[currentMove][fromY][fromX];
4946     if(gameInfo.variant == VariantShogi) {
4947         promotionZoneSize = 3;
4948         highestPromotingPiece = (int)WhiteFerz;
4949     }
4950
4951     // next weed out all moves that do not touch the promotion zone at all
4952     if((int)piece >= BlackPawn) {
4953         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4954              return FALSE;
4955         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4956     } else {
4957         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4958            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4959     }
4960
4961     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4962
4963     // weed out mandatory Shogi promotions
4964     if(gameInfo.variant == VariantShogi) {
4965         if(piece >= BlackPawn) {
4966             if(toY == 0 && piece == BlackPawn ||
4967                toY == 0 && piece == BlackQueen ||
4968                toY <= 1 && piece == BlackKnight) {
4969                 *promoChoice = '+';
4970                 return FALSE;
4971             }
4972         } else {
4973             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4974                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4975                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4976                 *promoChoice = '+';
4977                 return FALSE;
4978             }
4979         }
4980     }
4981
4982     // weed out obviously illegal Pawn moves
4983     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4984         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4985         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4986         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4987         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4988         // note we are not allowed to test for valid (non-)capture, due to premove
4989     }
4990
4991     // we either have a choice what to promote to, or (in Shogi) whether to promote
4992     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4993         *promoChoice = PieceToChar(BlackFerz);  // no choice
4994         return FALSE;
4995     }
4996     if(appData.alwaysPromoteToQueen) { // predetermined
4997         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4998              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4999         else *promoChoice = PieceToChar(BlackQueen);
5000         return FALSE;
5001     }
5002
5003     // suppress promotion popup on illegal moves that are not premoves
5004     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5005               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5006     if(appData.testLegality && !premove) {
5007         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5008                         epStatus[currentMove], castlingRights[currentMove],
5009                         fromY, fromX, toY, toX, NULLCHAR);
5010         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5011            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5012             return FALSE;
5013     }
5014
5015     return TRUE;
5016 }
5017
5018 int
5019 InPalace(row, column)
5020      int row, column;
5021 {   /* [HGM] for Xiangqi */
5022     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5023          column < (BOARD_WIDTH + 4)/2 &&
5024          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5025     return FALSE;
5026 }
5027
5028 int
5029 PieceForSquare (x, y)
5030      int x;
5031      int y;
5032 {
5033   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5034      return -1;
5035   else
5036      return boards[currentMove][y][x];
5037 }
5038
5039 int
5040 OKToStartUserMove(x, y)
5041      int x, y;
5042 {
5043     ChessSquare from_piece;
5044     int white_piece;
5045
5046     if (matchMode) return FALSE;
5047     if (gameMode == EditPosition) return TRUE;
5048
5049     if (x >= 0 && y >= 0)
5050       from_piece = boards[currentMove][y][x];
5051     else
5052       from_piece = EmptySquare;
5053
5054     if (from_piece == EmptySquare) return FALSE;
5055
5056     white_piece = (int)from_piece >= (int)WhitePawn &&
5057       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5058
5059     switch (gameMode) {
5060       case PlayFromGameFile:
5061       case AnalyzeFile:
5062       case TwoMachinesPlay:
5063       case EndOfGame:
5064         return FALSE;
5065
5066       case IcsObserving:
5067       case IcsIdle:
5068         return FALSE;
5069
5070       case MachinePlaysWhite:
5071       case IcsPlayingBlack:
5072         if (appData.zippyPlay) return FALSE;
5073         if (white_piece) {
5074             DisplayMoveError(_("You are playing Black"));
5075             return FALSE;
5076         }
5077         break;
5078
5079       case MachinePlaysBlack:
5080       case IcsPlayingWhite:
5081         if (appData.zippyPlay) return FALSE;
5082         if (!white_piece) {
5083             DisplayMoveError(_("You are playing White"));
5084             return FALSE;
5085         }
5086         break;
5087
5088       case EditGame:
5089         if (!white_piece && WhiteOnMove(currentMove)) {
5090             DisplayMoveError(_("It is White's turn"));
5091             return FALSE;
5092         }           
5093         if (white_piece && !WhiteOnMove(currentMove)) {
5094             DisplayMoveError(_("It is Black's turn"));
5095             return FALSE;
5096         }           
5097         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5098             /* Editing correspondence game history */
5099             /* Could disallow this or prompt for confirmation */
5100             cmailOldMove = -1;
5101         }
5102         if (currentMove < forwardMostMove) {
5103             /* Discarding moves */
5104             /* Could prompt for confirmation here,
5105                but I don't think that's such a good idea */
5106             forwardMostMove = currentMove;
5107         }
5108         break;
5109
5110       case BeginningOfGame:
5111         if (appData.icsActive) return FALSE;
5112         if (!appData.noChessProgram) {
5113             if (!white_piece) {
5114                 DisplayMoveError(_("You are playing White"));
5115                 return FALSE;
5116             }
5117         }
5118         break;
5119         
5120       case Training:
5121         if (!white_piece && WhiteOnMove(currentMove)) {
5122             DisplayMoveError(_("It is White's turn"));
5123             return FALSE;
5124         }           
5125         if (white_piece && !WhiteOnMove(currentMove)) {
5126             DisplayMoveError(_("It is Black's turn"));
5127             return FALSE;
5128         }           
5129         break;
5130
5131       default:
5132       case IcsExamining:
5133         break;
5134     }
5135     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5136         && gameMode != AnalyzeFile && gameMode != Training) {
5137         DisplayMoveError(_("Displayed position is not current"));
5138         return FALSE;
5139     }
5140     return TRUE;
5141 }
5142
5143 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5144 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5145 int lastLoadGameUseList = FALSE;
5146 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5147 ChessMove lastLoadGameStart = (ChessMove) 0;
5148
5149 ChessMove
5150 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5151      int fromX, fromY, toX, toY;
5152      int promoChar;
5153      Boolean captureOwn;
5154 {
5155     ChessMove moveType;
5156     ChessSquare pdown, pup;
5157
5158     /* Check if the user is playing in turn.  This is complicated because we
5159        let the user "pick up" a piece before it is his turn.  So the piece he
5160        tried to pick up may have been captured by the time he puts it down!
5161        Therefore we use the color the user is supposed to be playing in this
5162        test, not the color of the piece that is currently on the starting
5163        square---except in EditGame mode, where the user is playing both
5164        sides; fortunately there the capture race can't happen.  (It can
5165        now happen in IcsExamining mode, but that's just too bad.  The user
5166        will get a somewhat confusing message in that case.)
5167        */
5168
5169     switch (gameMode) {
5170       case PlayFromGameFile:
5171       case AnalyzeFile:
5172       case TwoMachinesPlay:
5173       case EndOfGame:
5174       case IcsObserving:
5175       case IcsIdle:
5176         /* We switched into a game mode where moves are not accepted,
5177            perhaps while the mouse button was down. */
5178         return ImpossibleMove;
5179
5180       case MachinePlaysWhite:
5181         /* User is moving for Black */
5182         if (WhiteOnMove(currentMove)) {
5183             DisplayMoveError(_("It is White's turn"));
5184             return ImpossibleMove;
5185         }
5186         break;
5187
5188       case MachinePlaysBlack:
5189         /* User is moving for White */
5190         if (!WhiteOnMove(currentMove)) {
5191             DisplayMoveError(_("It is Black's turn"));
5192             return ImpossibleMove;
5193         }
5194         break;
5195
5196       case EditGame:
5197       case IcsExamining:
5198       case BeginningOfGame:
5199       case AnalyzeMode:
5200       case Training:
5201         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5202             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5203             /* User is moving for Black */
5204             if (WhiteOnMove(currentMove)) {
5205                 DisplayMoveError(_("It is White's turn"));
5206                 return ImpossibleMove;
5207             }
5208         } else {
5209             /* User is moving for White */
5210             if (!WhiteOnMove(currentMove)) {
5211                 DisplayMoveError(_("It is Black's turn"));
5212                 return ImpossibleMove;
5213             }
5214         }
5215         break;
5216
5217       case IcsPlayingBlack:
5218         /* User is moving for Black */
5219         if (WhiteOnMove(currentMove)) {
5220             if (!appData.premove) {
5221                 DisplayMoveError(_("It is White's turn"));
5222             } else if (toX >= 0 && toY >= 0) {
5223                 premoveToX = toX;
5224                 premoveToY = toY;
5225                 premoveFromX = fromX;
5226                 premoveFromY = fromY;
5227                 premovePromoChar = promoChar;
5228                 gotPremove = 1;
5229                 if (appData.debugMode) 
5230                     fprintf(debugFP, "Got premove: fromX %d,"
5231                             "fromY %d, toX %d, toY %d\n",
5232                             fromX, fromY, toX, toY);
5233             }
5234             return ImpossibleMove;
5235         }
5236         break;
5237
5238       case IcsPlayingWhite:
5239         /* User is moving for White */
5240         if (!WhiteOnMove(currentMove)) {
5241             if (!appData.premove) {
5242                 DisplayMoveError(_("It is Black's turn"));
5243             } else if (toX >= 0 && toY >= 0) {
5244                 premoveToX = toX;
5245                 premoveToY = toY;
5246                 premoveFromX = fromX;
5247                 premoveFromY = fromY;
5248                 premovePromoChar = promoChar;
5249                 gotPremove = 1;
5250                 if (appData.debugMode) 
5251                     fprintf(debugFP, "Got premove: fromX %d,"
5252                             "fromY %d, toX %d, toY %d\n",
5253                             fromX, fromY, toX, toY);
5254             }
5255             return ImpossibleMove;
5256         }
5257         break;
5258
5259       default:
5260         break;
5261
5262       case EditPosition:
5263         /* EditPosition, empty square, or different color piece;
5264            click-click move is possible */
5265         if (toX == -2 || toY == -2) {
5266             boards[0][fromY][fromX] = EmptySquare;
5267             return AmbiguousMove;
5268         } else if (toX >= 0 && toY >= 0) {
5269             boards[0][toY][toX] = boards[0][fromY][fromX];
5270             boards[0][fromY][fromX] = EmptySquare;
5271             return AmbiguousMove;
5272         }
5273         return ImpossibleMove;
5274     }
5275
5276     if(toX < 0 || toY < 0) return ImpossibleMove;
5277     pdown = boards[currentMove][fromY][fromX];
5278     pup = boards[currentMove][toY][toX];
5279
5280     /* [HGM] If move started in holdings, it means a drop */
5281     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5282          if( pup != EmptySquare ) return ImpossibleMove;
5283          if(appData.testLegality) {
5284              /* it would be more logical if LegalityTest() also figured out
5285               * which drops are legal. For now we forbid pawns on back rank.
5286               * Shogi is on its own here...
5287               */
5288              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5289                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5290                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5291          }
5292          return WhiteDrop; /* Not needed to specify white or black yet */
5293     }
5294
5295     userOfferedDraw = FALSE;
5296         
5297     /* [HGM] always test for legality, to get promotion info */
5298     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5299                           epStatus[currentMove], castlingRights[currentMove],
5300                                          fromY, fromX, toY, toX, promoChar);
5301     /* [HGM] but possibly ignore an IllegalMove result */
5302     if (appData.testLegality) {
5303         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304             DisplayMoveError(_("Illegal move"));
5305             return ImpossibleMove;
5306         }
5307     }
5308 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5309     return moveType;
5310     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311        function is made into one that returns an OK move type if FinishMove
5312        should be called. This to give the calling driver routine the
5313        opportunity to finish the userMove input with a promotion popup,
5314        without bothering the user with this for invalid or illegal moves */
5315
5316 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5317 }
5318
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5320 int
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5322      ChessMove moveType;
5323      int fromX, fromY, toX, toY;
5324      /*char*/int promoChar;
5325 {
5326     char *bookHit = 0;
5327 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5328     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5329         // [HGM] superchess: suppress promotions to non-available piece
5330         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5331         if(WhiteOnMove(currentMove)) {
5332             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5333         } else {
5334             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5335         }
5336     }
5337
5338     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5339        move type in caller when we know the move is a legal promotion */
5340     if(moveType == NormalMove && promoChar)
5341         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5342 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5343     /* [HGM] convert drag-and-drop piece drops to standard form */
5344     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5345          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5346            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5347                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5348 //         fromX = boards[currentMove][fromY][fromX];
5349            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5350            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5351            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5352            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5353          fromY = DROP_RANK;
5354     }
5355
5356     /* [HGM] <popupFix> The following if has been moved here from
5357        UserMoveEvent(). Because it seemed to belon here (why not allow
5358        piece drops in training games?), and because it can only be
5359        performed after it is known to what we promote. */
5360     if (gameMode == Training) {
5361       /* compare the move played on the board to the next move in the
5362        * game. If they match, display the move and the opponent's response. 
5363        * If they don't match, display an error message.
5364        */
5365       int saveAnimate;
5366       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5367       CopyBoard(testBoard, boards[currentMove]);
5368       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5369
5370       if (CompareBoards(testBoard, boards[currentMove+1])) {
5371         ForwardInner(currentMove+1);
5372
5373         /* Autoplay the opponent's response.
5374          * if appData.animate was TRUE when Training mode was entered,
5375          * the response will be animated.
5376          */
5377         saveAnimate = appData.animate;
5378         appData.animate = animateTraining;
5379         ForwardInner(currentMove+1);
5380         appData.animate = saveAnimate;
5381
5382         /* check for the end of the game */
5383         if (currentMove >= forwardMostMove) {
5384           gameMode = PlayFromGameFile;
5385           ModeHighlight();
5386           SetTrainingModeOff();
5387           DisplayInformation(_("End of game"));
5388         }
5389       } else {
5390         DisplayError(_("Incorrect move"), 0);
5391       }
5392       return 1;
5393     }
5394
5395   /* Ok, now we know that the move is good, so we can kill
5396      the previous line in Analysis Mode */
5397   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5398     forwardMostMove = currentMove;
5399   }
5400
5401   /* If we need the chess program but it's dead, restart it */
5402   ResurrectChessProgram();
5403
5404   /* A user move restarts a paused game*/
5405   if (pausing)
5406     PauseEvent();
5407
5408   thinkOutput[0] = NULLCHAR;
5409
5410   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5411
5412   if (gameMode == BeginningOfGame) {
5413     if (appData.noChessProgram) {
5414       gameMode = EditGame;
5415       SetGameInfo();
5416     } else {
5417       char buf[MSG_SIZ];
5418       gameMode = MachinePlaysBlack;
5419       StartClocks();
5420       SetGameInfo();
5421       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5422       DisplayTitle(buf);
5423       if (first.sendName) {
5424         sprintf(buf, "name %s\n", gameInfo.white);
5425         SendToProgram(buf, &first);
5426       }
5427       StartClocks();
5428     }
5429     ModeHighlight();
5430   }
5431 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5432   /* Relay move to ICS or chess engine */
5433   if (appData.icsActive) {
5434     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5435         gameMode == IcsExamining) {
5436       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5437       ics_user_moved = 1;
5438     }
5439   } else {
5440     if (first.sendTime && (gameMode == BeginningOfGame ||
5441                            gameMode == MachinePlaysWhite ||
5442                            gameMode == MachinePlaysBlack)) {
5443       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5444     }
5445     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5446          // [HGM] book: if program might be playing, let it use book
5447         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5448         first.maybeThinking = TRUE;
5449     } else SendMoveToProgram(forwardMostMove-1, &first);
5450     if (currentMove == cmailOldMove + 1) {
5451       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5452     }
5453   }
5454
5455   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5456
5457   switch (gameMode) {
5458   case EditGame:
5459     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5460                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5461     case MT_NONE:
5462     case MT_CHECK:
5463       break;
5464     case MT_CHECKMATE:
5465     case MT_STAINMATE:
5466       if (WhiteOnMove(currentMove)) {
5467         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5468       } else {
5469         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5470       }
5471       break;
5472     case MT_STALEMATE:
5473       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5474       break;
5475     }
5476     break;
5477     
5478   case MachinePlaysBlack:
5479   case MachinePlaysWhite:
5480     /* disable certain menu options while machine is thinking */
5481     SetMachineThinkingEnables();
5482     break;
5483
5484   default:
5485     break;
5486   }
5487
5488   if(bookHit) { // [HGM] book: simulate book reply
5489         static char bookMove[MSG_SIZ]; // a bit generous?
5490
5491         programStats.nodes = programStats.depth = programStats.time = 
5492         programStats.score = programStats.got_only_move = 0;
5493         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5494
5495         strcpy(bookMove, "move ");
5496         strcat(bookMove, bookHit);
5497         HandleMachineMove(bookMove, &first);
5498   }
5499   return 1;
5500 }
5501
5502 void
5503 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5504      int fromX, fromY, toX, toY;
5505      int promoChar;
5506 {
5507     /* [HGM] This routine was added to allow calling of its two logical
5508        parts from other modules in the old way. Before, UserMoveEvent()
5509        automatically called FinishMove() if the move was OK, and returned
5510        otherwise. I separated the two, in order to make it possible to
5511        slip a promotion popup in between. But that it always needs two
5512        calls, to the first part, (now called UserMoveTest() ), and to
5513        FinishMove if the first part succeeded. Calls that do not need
5514        to do anything in between, can call this routine the old way. 
5515     */
5516     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5517 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5518     if(moveType == AmbiguousMove)
5519         DrawPosition(FALSE, boards[currentMove]);
5520     else if(moveType != ImpossibleMove && moveType != Comment)
5521         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5522 }
5523
5524 void LeftClick(ClickType clickType, int xPix, int yPix)
5525 {
5526     int x, y;
5527     Boolean saveAnimate;
5528     static int second = 0, promotionChoice = 0;
5529     char promoChoice = NULLCHAR;
5530
5531     if (clickType == Press) ErrorPopDown();
5532
5533     x = EventToSquare(xPix, BOARD_WIDTH);
5534     y = EventToSquare(yPix, BOARD_HEIGHT);
5535     if (!flipView && y >= 0) {
5536         y = BOARD_HEIGHT - 1 - y;
5537     }
5538     if (flipView && x >= 0) {
5539         x = BOARD_WIDTH - 1 - x;
5540     }
5541
5542     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5543         if(clickType == Release) return; // ignore upclick of click-click destination
5544         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5545         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5546         if(gameInfo.holdingsWidth && 
5547                 (WhiteOnMove(currentMove) 
5548                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5549                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5550             // click in right holdings, for determining promotion piece
5551             ChessSquare p = boards[currentMove][y][x];
5552             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5553             if(p != EmptySquare) {
5554                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5555                 fromX = fromY = -1;
5556                 return;
5557             }
5558         }
5559         DrawPosition(FALSE, boards[currentMove]);
5560         return;
5561     }
5562
5563     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5564     if(clickType == Press
5565             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5566               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5567               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5568         return;
5569
5570     if (fromX == -1) {
5571         if (clickType == Press) {
5572             /* First square */
5573             if (OKToStartUserMove(x, y)) {
5574                 fromX = x;
5575                 fromY = y;
5576                 second = 0;
5577                 DragPieceBegin(xPix, yPix);
5578                 if (appData.highlightDragging) {
5579                     SetHighlights(x, y, -1, -1);
5580                 }
5581             }
5582         }
5583         return;
5584     }
5585
5586     /* fromX != -1 */
5587     if (clickType == Press && gameMode != EditPosition) {
5588         ChessSquare fromP;
5589         ChessSquare toP;
5590         int frc;
5591
5592         // ignore off-board to clicks
5593         if(y < 0 || x < 0) return;
5594
5595         /* Check if clicking again on the same color piece */
5596         fromP = boards[currentMove][fromY][fromX];
5597         toP = boards[currentMove][y][x];
5598         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5599         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5600              WhitePawn <= toP && toP <= WhiteKing &&
5601              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5602              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5603             (BlackPawn <= fromP && fromP <= BlackKing && 
5604              BlackPawn <= toP && toP <= BlackKing &&
5605              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5606              !(fromP == BlackKing && toP == BlackRook && frc))) {
5607             /* Clicked again on same color piece -- changed his mind */
5608             second = (x == fromX && y == fromY);
5609             if (appData.highlightDragging) {
5610                 SetHighlights(x, y, -1, -1);
5611             } else {
5612                 ClearHighlights();
5613             }
5614             if (OKToStartUserMove(x, y)) {
5615                 fromX = x;
5616                 fromY = y;
5617                 DragPieceBegin(xPix, yPix);
5618             }
5619             return;
5620         }
5621         // ignore clicks on holdings
5622         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5623     }
5624
5625     if (clickType == Release && x == fromX && y == fromY) {
5626         DragPieceEnd(xPix, yPix);
5627         if (appData.animateDragging) {
5628             /* Undo animation damage if any */
5629             DrawPosition(FALSE, NULL);
5630         }
5631         if (second) {
5632             /* Second up/down in same square; just abort move */
5633             second = 0;
5634             fromX = fromY = -1;
5635             ClearHighlights();
5636             gotPremove = 0;
5637             ClearPremoveHighlights();
5638         } else {
5639             /* First upclick in same square; start click-click mode */
5640             SetHighlights(x, y, -1, -1);
5641         }
5642         return;
5643     }
5644
5645     /* we now have a different from- and (possibly off-board) to-square */
5646     /* Completed move */
5647     toX = x;
5648     toY = y;
5649     saveAnimate = appData.animate;
5650     if (clickType == Press) {
5651         /* Finish clickclick move */
5652         if (appData.animate || appData.highlightLastMove) {
5653             SetHighlights(fromX, fromY, toX, toY);
5654         } else {
5655             ClearHighlights();
5656         }
5657     } else {
5658         /* Finish drag move */
5659         if (appData.highlightLastMove) {
5660             SetHighlights(fromX, fromY, toX, toY);
5661         } else {
5662             ClearHighlights();
5663         }
5664         DragPieceEnd(xPix, yPix);
5665         /* Don't animate move and drag both */
5666         appData.animate = FALSE;
5667     }
5668
5669     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5670     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5671         ClearHighlights();
5672         fromX = fromY = -1;
5673         DrawPosition(FALSE, NULL);
5674         return;
5675     }
5676
5677     // off-board moves should not be highlighted
5678     if(x < 0 || x < 0) ClearHighlights();
5679
5680     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5681         SetHighlights(fromX, fromY, toX, toY);
5682         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5683             // [HGM] super: promotion to captured piece selected from holdings
5684             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5685             promotionChoice = TRUE;
5686             // kludge follows to temporarily execute move on display, without promoting yet
5687             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5688             boards[currentMove][toY][toX] = p;
5689             DrawPosition(FALSE, boards[currentMove]);
5690             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5691             boards[currentMove][toY][toX] = q;
5692             DisplayMessage("Click in holdings to choose piece", "");
5693             return;
5694         }
5695         PromotionPopUp();
5696     } else {
5697         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5698         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5699         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5700         fromX = fromY = -1;
5701     }
5702     appData.animate = saveAnimate;
5703     if (appData.animate || appData.animateDragging) {
5704         /* Undo animation damage if needed */
5705         DrawPosition(FALSE, NULL);
5706     }
5707 }
5708
5709 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5710 {
5711 //    char * hint = lastHint;
5712     FrontEndProgramStats stats;
5713
5714     stats.which = cps == &first ? 0 : 1;
5715     stats.depth = cpstats->depth;
5716     stats.nodes = cpstats->nodes;
5717     stats.score = cpstats->score;
5718     stats.time = cpstats->time;
5719     stats.pv = cpstats->movelist;
5720     stats.hint = lastHint;
5721     stats.an_move_index = 0;
5722     stats.an_move_count = 0;
5723
5724     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5725         stats.hint = cpstats->move_name;
5726         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5727         stats.an_move_count = cpstats->nr_moves;
5728     }
5729
5730     SetProgramStats( &stats );
5731 }
5732
5733 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5734 {   // [HGM] book: this routine intercepts moves to simulate book replies
5735     char *bookHit = NULL;
5736
5737     //first determine if the incoming move brings opponent into his book
5738     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5739         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5740     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5741     if(bookHit != NULL && !cps->bookSuspend) {
5742         // make sure opponent is not going to reply after receiving move to book position
5743         SendToProgram("force\n", cps);
5744         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5745     }
5746     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5747     // now arrange restart after book miss
5748     if(bookHit) {
5749         // after a book hit we never send 'go', and the code after the call to this routine
5750         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5751         char buf[MSG_SIZ];
5752         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5753         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5754         SendToProgram(buf, cps);
5755         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5756     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5757         SendToProgram("go\n", cps);
5758         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5759     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5760         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5761             SendToProgram("go\n", cps); 
5762         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5763     }
5764     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5765 }
5766
5767 char *savedMessage;
5768 ChessProgramState *savedState;
5769 void DeferredBookMove(void)
5770 {
5771         if(savedState->lastPing != savedState->lastPong)
5772                     ScheduleDelayedEvent(DeferredBookMove, 10);
5773         else
5774         HandleMachineMove(savedMessage, savedState);
5775 }
5776
5777 void
5778 HandleMachineMove(message, cps)
5779      char *message;
5780      ChessProgramState *cps;
5781 {
5782     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5783     char realname[MSG_SIZ];
5784     int fromX, fromY, toX, toY;
5785     ChessMove moveType;
5786     char promoChar;
5787     char *p;
5788     int machineWhite;
5789     char *bookHit;
5790
5791 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5792     /*
5793      * Kludge to ignore BEL characters
5794      */
5795     while (*message == '\007') message++;
5796
5797     /*
5798      * [HGM] engine debug message: ignore lines starting with '#' character
5799      */
5800     if(cps->debug && *message == '#') return;
5801
5802     /*
5803      * Look for book output
5804      */
5805     if (cps == &first && bookRequested) {
5806         if (message[0] == '\t' || message[0] == ' ') {
5807             /* Part of the book output is here; append it */
5808             strcat(bookOutput, message);
5809             strcat(bookOutput, "  \n");
5810             return;
5811         } else if (bookOutput[0] != NULLCHAR) {
5812             /* All of book output has arrived; display it */
5813             char *p = bookOutput;
5814             while (*p != NULLCHAR) {
5815                 if (*p == '\t') *p = ' ';
5816                 p++;
5817             }
5818             DisplayInformation(bookOutput);
5819             bookRequested = FALSE;
5820             /* Fall through to parse the current output */
5821         }
5822     }
5823
5824     /*
5825      * Look for machine move.
5826      */
5827     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5828         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5829     {
5830         /* This method is only useful on engines that support ping */
5831         if (cps->lastPing != cps->lastPong) {
5832           if (gameMode == BeginningOfGame) {
5833             /* Extra move from before last new; ignore */
5834             if (appData.debugMode) {
5835                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5836             }
5837           } else {
5838             if (appData.debugMode) {
5839                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5840                         cps->which, gameMode);
5841             }
5842
5843             SendToProgram("undo\n", cps);
5844           }
5845           return;
5846         }
5847
5848         switch (gameMode) {
5849           case BeginningOfGame:
5850             /* Extra move from before last reset; ignore */
5851             if (appData.debugMode) {
5852                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5853             }
5854             return;
5855
5856           case EndOfGame:
5857           case IcsIdle:
5858           default:
5859             /* Extra move after we tried to stop.  The mode test is
5860                not a reliable way of detecting this problem, but it's
5861                the best we can do on engines that don't support ping.
5862             */
5863             if (appData.debugMode) {
5864                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5865                         cps->which, gameMode);
5866             }
5867             SendToProgram("undo\n", cps);
5868             return;
5869
5870           case MachinePlaysWhite:
5871           case IcsPlayingWhite:
5872             machineWhite = TRUE;
5873             break;
5874
5875           case MachinePlaysBlack:
5876           case IcsPlayingBlack:
5877             machineWhite = FALSE;
5878             break;
5879
5880           case TwoMachinesPlay:
5881             machineWhite = (cps->twoMachinesColor[0] == 'w');
5882             break;
5883         }
5884         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5885             if (appData.debugMode) {
5886                 fprintf(debugFP,
5887                         "Ignoring move out of turn by %s, gameMode %d"
5888                         ", forwardMost %d\n",
5889                         cps->which, gameMode, forwardMostMove);
5890             }
5891             return;
5892         }
5893
5894     if (appData.debugMode) { int f = forwardMostMove;
5895         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5896                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5897     }
5898         if(cps->alphaRank) AlphaRank(machineMove, 4);
5899         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5900                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5901             /* Machine move could not be parsed; ignore it. */
5902             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5903                     machineMove, cps->which);
5904             DisplayError(buf1, 0);
5905             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5906                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5907             if (gameMode == TwoMachinesPlay) {
5908               GameEnds(machineWhite ? BlackWins : WhiteWins,
5909                        buf1, GE_XBOARD);
5910             }
5911             return;
5912         }
5913
5914         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5915         /* So we have to redo legality test with true e.p. status here,  */
5916         /* to make sure an illegal e.p. capture does not slip through,   */
5917         /* to cause a forfeit on a justified illegal-move complaint      */
5918         /* of the opponent.                                              */
5919         if( gameMode==TwoMachinesPlay && appData.testLegality
5920             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5921                                                               ) {
5922            ChessMove moveType;
5923            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5924                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5925                              fromY, fromX, toY, toX, promoChar);
5926             if (appData.debugMode) {
5927                 int i;
5928                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5929                     castlingRights[forwardMostMove][i], castlingRank[i]);
5930                 fprintf(debugFP, "castling rights\n");
5931             }
5932             if(moveType == IllegalMove) {
5933                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5934                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5935                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5936                            buf1, GE_XBOARD);
5937                 return;
5938            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5939            /* [HGM] Kludge to handle engines that send FRC-style castling
5940               when they shouldn't (like TSCP-Gothic) */
5941            switch(moveType) {
5942              case WhiteASideCastleFR:
5943              case BlackASideCastleFR:
5944                toX+=2;
5945                currentMoveString[2]++;
5946                break;
5947              case WhiteHSideCastleFR:
5948              case BlackHSideCastleFR:
5949                toX--;
5950                currentMoveString[2]--;
5951                break;
5952              default: ; // nothing to do, but suppresses warning of pedantic compilers
5953            }
5954         }
5955         hintRequested = FALSE;
5956         lastHint[0] = NULLCHAR;
5957         bookRequested = FALSE;
5958         /* Program may be pondering now */
5959         cps->maybeThinking = TRUE;
5960         if (cps->sendTime == 2) cps->sendTime = 1;
5961         if (cps->offeredDraw) cps->offeredDraw--;
5962
5963 #if ZIPPY
5964         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5965             first.initDone) {
5966           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5967           ics_user_moved = 1;
5968           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5969                 char buf[3*MSG_SIZ];
5970
5971                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5972                         programStats.score / 100.,
5973                         programStats.depth,
5974                         programStats.time / 100.,
5975                         (unsigned int)programStats.nodes,
5976                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5977                         programStats.movelist);
5978                 SendToICS(buf);
5979 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5980           }
5981         }
5982 #endif
5983         /* currentMoveString is set as a side-effect of ParseOneMove */
5984         strcpy(machineMove, currentMoveString);
5985         strcat(machineMove, "\n");
5986         strcpy(moveList[forwardMostMove], machineMove);
5987
5988         /* [AS] Save move info and clear stats for next move */
5989         pvInfoList[ forwardMostMove ].score = programStats.score;
5990         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5991         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5992         ClearProgramStats();
5993         thinkOutput[0] = NULLCHAR;
5994         hiddenThinkOutputState = 0;
5995
5996         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5997
5998         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5999         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6000             int count = 0;
6001
6002             while( count < adjudicateLossPlies ) {
6003                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6004
6005                 if( count & 1 ) {
6006                     score = -score; /* Flip score for winning side */
6007                 }
6008
6009                 if( score > adjudicateLossThreshold ) {
6010                     break;
6011                 }
6012
6013                 count++;
6014             }
6015
6016             if( count >= adjudicateLossPlies ) {
6017                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6018
6019                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6020                     "Xboard adjudication", 
6021                     GE_XBOARD );
6022
6023                 return;
6024             }
6025         }
6026
6027         if( gameMode == TwoMachinesPlay ) {
6028           // [HGM] some adjudications useful with buggy engines
6029             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6030           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6031
6032
6033             if( appData.testLegality )
6034             {   /* [HGM] Some more adjudications for obstinate engines */
6035                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6036                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6037                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6038                 static int moveCount = 6;
6039                 ChessMove result;
6040                 char *reason = NULL;
6041
6042                 /* Count what is on board. */
6043                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6044                 {   ChessSquare p = boards[forwardMostMove][i][j];
6045                     int m=i;
6046
6047                     switch((int) p)
6048                     {   /* count B,N,R and other of each side */
6049                         case WhiteKing:
6050                         case BlackKing:
6051                              NrK++; break; // [HGM] atomic: count Kings
6052                         case WhiteKnight:
6053                              NrWN++; break;
6054                         case WhiteBishop:
6055                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6056                              bishopsColor |= 1 << ((i^j)&1);
6057                              NrWB++; break;
6058                         case BlackKnight:
6059                              NrBN++; break;
6060                         case BlackBishop:
6061                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6062                              bishopsColor |= 1 << ((i^j)&1);
6063                              NrBB++; break;
6064                         case WhiteRook:
6065                              NrWR++; break;
6066                         case BlackRook:
6067                              NrBR++; break;
6068                         case WhiteQueen:
6069                              NrWQ++; break;
6070                         case BlackQueen:
6071                              NrBQ++; break;
6072                         case EmptySquare: 
6073                              break;
6074                         case BlackPawn:
6075                              m = 7-i;
6076                         case WhitePawn:
6077                              PawnAdvance += m; NrPawns++;
6078                     }
6079                     NrPieces += (p != EmptySquare);
6080                     NrW += ((int)p < (int)BlackPawn);
6081                     if(gameInfo.variant == VariantXiangqi && 
6082                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6083                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6084                         NrW -= ((int)p < (int)BlackPawn);
6085                     }
6086                 }
6087
6088                 /* Some material-based adjudications that have to be made before stalemate test */
6089                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6090                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6091                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6092                      if(appData.checkMates) {
6093                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6094                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6095                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6096                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6097                          return;
6098                      }
6099                 }
6100
6101                 /* Bare King in Shatranj (loses) or Losers (wins) */
6102                 if( NrW == 1 || NrPieces - NrW == 1) {
6103                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6104                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6105                      if(appData.checkMates) {
6106                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6107                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6108                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6109                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6110                          return;
6111                      }
6112                   } else
6113                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6114                   {    /* bare King */
6115                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6116                         if(appData.checkMates) {
6117                             /* but only adjudicate if adjudication enabled */
6118                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6119                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6120                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6121                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6122                             return;
6123                         }
6124                   }
6125                 } else bare = 1;
6126
6127
6128             // don't wait for engine to announce game end if we can judge ourselves
6129             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6130                                        castlingRights[forwardMostMove]) ) {
6131               case MT_CHECK:
6132                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6133                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6134                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6135                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6136                             checkCnt++;
6137                         if(checkCnt >= 2) {
6138                             reason = "Xboard adjudication: 3rd check";
6139                             epStatus[forwardMostMove] = EP_CHECKMATE;
6140                             break;
6141                         }
6142                     }
6143                 }
6144               case MT_NONE:
6145               default:
6146                 break;
6147               case MT_STALEMATE:
6148               case MT_STAINMATE:
6149                 reason = "Xboard adjudication: Stalemate";
6150                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6151                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6152                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6153                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6154                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6155                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6156                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6157                                                                         EP_CHECKMATE : EP_WINS);
6158                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6159                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6160                 }
6161                 break;
6162               case MT_CHECKMATE:
6163                 reason = "Xboard adjudication: Checkmate";
6164                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6165                 break;
6166             }
6167
6168                 switch(i = epStatus[forwardMostMove]) {
6169                     case EP_STALEMATE:
6170                         result = GameIsDrawn; break;
6171                     case EP_CHECKMATE:
6172                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6173                     case EP_WINS:
6174                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6175                     default:
6176                         result = (ChessMove) 0;
6177                 }
6178                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6179                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6180                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6181                     GameEnds( result, reason, GE_XBOARD );
6182                     return;
6183                 }
6184
6185                 /* Next absolutely insufficient mating material. */
6186                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6187                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6188                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6189                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6190                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6191
6192                      /* always flag draws, for judging claims */
6193                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6194
6195                      if(appData.materialDraws) {
6196                          /* but only adjudicate them if adjudication enabled */
6197                          SendToProgram("force\n", cps->other); // suppress reply
6198                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6199                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6200                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6201                          return;
6202                      }
6203                 }
6204
6205                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6206                 if(NrPieces == 4 && 
6207                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6208                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6209                    || NrWN==2 || NrBN==2     /* KNNK */
6210                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6211                   ) ) {
6212                      if(--moveCount < 0 && appData.trivialDraws)
6213                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6214                           SendToProgram("force\n", cps->other); // suppress reply
6215                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6216                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6218                           return;
6219                      }
6220                 } else moveCount = 6;
6221             }
6222           }
6223           
6224           if (appData.debugMode) { int i;
6225             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6226                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6227                     appData.drawRepeats);
6228             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6229               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6230             
6231           }
6232
6233                 /* Check for rep-draws */
6234                 count = 0;
6235                 for(k = forwardMostMove-2;
6236                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6237                         epStatus[k] < EP_UNKNOWN &&
6238                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6239                     k-=2)
6240                 {   int rights=0;
6241                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6242                         /* compare castling rights */
6243                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6244                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6245                                 rights++; /* King lost rights, while rook still had them */
6246                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6247                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6248                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6249                                    rights++; /* but at least one rook lost them */
6250                         }
6251                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6252                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6253                                 rights++; 
6254                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6255                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6256                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6257                                    rights++;
6258                         }
6259                         if( rights == 0 && ++count > appData.drawRepeats-2
6260                             && appData.drawRepeats > 1) {
6261                              /* adjudicate after user-specified nr of repeats */
6262                              SendToProgram("force\n", cps->other); // suppress reply
6263                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6264                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6265                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6266                                 // [HGM] xiangqi: check for forbidden perpetuals
6267                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6268                                 for(m=forwardMostMove; m>k; m-=2) {
6269                                     if(MateTest(boards[m], PosFlags(m), 
6270                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6271                                         ourPerpetual = 0; // the current mover did not always check
6272                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6273                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6274                                         hisPerpetual = 0; // the opponent did not always check
6275                                 }
6276                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6277                                                                         ourPerpetual, hisPerpetual);
6278                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6279                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6280                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6281                                     return;
6282                                 }
6283                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6284                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6285                                 // Now check for perpetual chases
6286                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6287                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6288                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6289                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6290                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6291                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6292                                         return;
6293                                     }
6294                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6295                                         break; // Abort repetition-checking loop.
6296                                 }
6297                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6298                              }
6299                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6300                              return;
6301                         }
6302                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6303                              epStatus[forwardMostMove] = EP_REP_DRAW;
6304                     }
6305                 }
6306
6307                 /* Now we test for 50-move draws. Determine ply count */
6308                 count = forwardMostMove;
6309                 /* look for last irreversble move */
6310                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6311                     count--;
6312                 /* if we hit starting position, add initial plies */
6313                 if( count == backwardMostMove )
6314                     count -= initialRulePlies;
6315                 count = forwardMostMove - count; 
6316                 if( count >= 100)
6317                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6318                          /* this is used to judge if draw claims are legal */
6319                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6320                          SendToProgram("force\n", cps->other); // suppress reply
6321                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6322                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6323                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6324                          return;
6325                 }
6326
6327                 /* if draw offer is pending, treat it as a draw claim
6328                  * when draw condition present, to allow engines a way to
6329                  * claim draws before making their move to avoid a race
6330                  * condition occurring after their move
6331                  */
6332                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6333                          char *p = NULL;
6334                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6335                              p = "Draw claim: 50-move rule";
6336                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6337                              p = "Draw claim: 3-fold repetition";
6338                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6339                              p = "Draw claim: insufficient mating material";
6340                          if( p != NULL ) {
6341                              SendToProgram("force\n", cps->other); // suppress reply
6342                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6343                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6344                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6345                              return;
6346                          }
6347                 }
6348
6349
6350                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6351                     SendToProgram("force\n", cps->other); // suppress reply
6352                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6353                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6354
6355                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6356
6357                     return;
6358                 }
6359         }
6360
6361         bookHit = NULL;
6362         if (gameMode == TwoMachinesPlay) {
6363             /* [HGM] relaying draw offers moved to after reception of move */
6364             /* and interpreting offer as claim if it brings draw condition */
6365             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6366                 SendToProgram("draw\n", cps->other);
6367             }
6368             if (cps->other->sendTime) {
6369                 SendTimeRemaining(cps->other,
6370                                   cps->other->twoMachinesColor[0] == 'w');
6371             }
6372             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6373             if (firstMove && !bookHit) {
6374                 firstMove = FALSE;
6375                 if (cps->other->useColors) {
6376                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6377                 }
6378                 SendToProgram("go\n", cps->other);
6379             }
6380             cps->other->maybeThinking = TRUE;
6381         }
6382
6383         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6384         
6385         if (!pausing && appData.ringBellAfterMoves) {
6386             RingBell();
6387         }
6388
6389         /* 
6390          * Reenable menu items that were disabled while
6391          * machine was thinking
6392          */
6393         if (gameMode != TwoMachinesPlay)
6394             SetUserThinkingEnables();
6395
6396         // [HGM] book: after book hit opponent has received move and is now in force mode
6397         // force the book reply into it, and then fake that it outputted this move by jumping
6398         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6399         if(bookHit) {
6400                 static char bookMove[MSG_SIZ]; // a bit generous?
6401
6402                 strcpy(bookMove, "move ");
6403                 strcat(bookMove, bookHit);
6404                 message = bookMove;
6405                 cps = cps->other;
6406                 programStats.nodes = programStats.depth = programStats.time = 
6407                 programStats.score = programStats.got_only_move = 0;
6408                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6409
6410                 if(cps->lastPing != cps->lastPong) {
6411                     savedMessage = message; // args for deferred call
6412                     savedState = cps;
6413                     ScheduleDelayedEvent(DeferredBookMove, 10);
6414                     return;
6415                 }
6416                 goto FakeBookMove;
6417         }
6418
6419         return;
6420     }
6421
6422     /* Set special modes for chess engines.  Later something general
6423      *  could be added here; for now there is just one kludge feature,
6424      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6425      *  when "xboard" is given as an interactive command.
6426      */
6427     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6428         cps->useSigint = FALSE;
6429         cps->useSigterm = FALSE;
6430     }
6431     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6432       ParseFeatures(message+8, cps);
6433       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6434     }
6435
6436     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6437      * want this, I was asked to put it in, and obliged.
6438      */
6439     if (!strncmp(message, "setboard ", 9)) {
6440         Board initial_position; int i;
6441
6442         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6443
6444         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6445             DisplayError(_("Bad FEN received from engine"), 0);
6446             return ;
6447         } else {
6448            Reset(TRUE, FALSE);
6449            CopyBoard(boards[0], initial_position);
6450            initialRulePlies = FENrulePlies;
6451            epStatus[0] = FENepStatus;
6452            for( i=0; i<nrCastlingRights; i++ )
6453                 castlingRights[0][i] = FENcastlingRights[i];
6454            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6455            else gameMode = MachinePlaysBlack;                 
6456            DrawPosition(FALSE, boards[currentMove]);
6457         }
6458         return;
6459     }
6460
6461     /*
6462      * Look for communication commands
6463      */
6464     if (!strncmp(message, "telluser ", 9)) {
6465         DisplayNote(message + 9);
6466         return;
6467     }
6468     if (!strncmp(message, "tellusererror ", 14)) {
6469         DisplayError(message + 14, 0);
6470         return;
6471     }
6472     if (!strncmp(message, "tellopponent ", 13)) {
6473       if (appData.icsActive) {
6474         if (loggedOn) {
6475           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6476           SendToICS(buf1);
6477         }
6478       } else {
6479         DisplayNote(message + 13);
6480       }
6481       return;
6482     }
6483     if (!strncmp(message, "tellothers ", 11)) {
6484       if (appData.icsActive) {
6485         if (loggedOn) {
6486           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6487           SendToICS(buf1);
6488         }
6489       }
6490       return;
6491     }
6492     if (!strncmp(message, "tellall ", 8)) {
6493       if (appData.icsActive) {
6494         if (loggedOn) {
6495           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6496           SendToICS(buf1);
6497         }
6498       } else {
6499         DisplayNote(message + 8);
6500       }
6501       return;
6502     }
6503     if (strncmp(message, "warning", 7) == 0) {
6504         /* Undocumented feature, use tellusererror in new code */
6505         DisplayError(message, 0);
6506         return;
6507     }
6508     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6509         strcpy(realname, cps->tidy);
6510         strcat(realname, " query");
6511         AskQuestion(realname, buf2, buf1, cps->pr);
6512         return;
6513     }
6514     /* Commands from the engine directly to ICS.  We don't allow these to be 
6515      *  sent until we are logged on. Crafty kibitzes have been known to 
6516      *  interfere with the login process.
6517      */
6518     if (loggedOn) {
6519         if (!strncmp(message, "tellics ", 8)) {
6520             SendToICS(message + 8);
6521             SendToICS("\n");
6522             return;
6523         }
6524         if (!strncmp(message, "tellicsnoalias ", 15)) {
6525             SendToICS(ics_prefix);
6526             SendToICS(message + 15);
6527             SendToICS("\n");
6528             return;
6529         }
6530         /* The following are for backward compatibility only */
6531         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6532             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6533             SendToICS(ics_prefix);
6534             SendToICS(message);
6535             SendToICS("\n");
6536             return;
6537         }
6538     }
6539     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6540         return;
6541     }
6542     /*
6543      * If the move is illegal, cancel it and redraw the board.
6544      * Also deal with other error cases.  Matching is rather loose
6545      * here to accommodate engines written before the spec.
6546      */
6547     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6548         strncmp(message, "Error", 5) == 0) {
6549         if (StrStr(message, "name") || 
6550             StrStr(message, "rating") || StrStr(message, "?") ||
6551             StrStr(message, "result") || StrStr(message, "board") ||
6552             StrStr(message, "bk") || StrStr(message, "computer") ||
6553             StrStr(message, "variant") || StrStr(message, "hint") ||
6554             StrStr(message, "random") || StrStr(message, "depth") ||
6555             StrStr(message, "accepted")) {
6556             return;
6557         }
6558         if (StrStr(message, "protover")) {
6559           /* Program is responding to input, so it's apparently done
6560              initializing, and this error message indicates it is
6561              protocol version 1.  So we don't need to wait any longer
6562              for it to initialize and send feature commands. */
6563           FeatureDone(cps, 1);
6564           cps->protocolVersion = 1;
6565           return;
6566         }
6567         cps->maybeThinking = FALSE;
6568
6569         if (StrStr(message, "draw")) {
6570             /* Program doesn't have "draw" command */
6571             cps->sendDrawOffers = 0;
6572             return;
6573         }
6574         if (cps->sendTime != 1 &&
6575             (StrStr(message, "time") || StrStr(message, "otim"))) {
6576           /* Program apparently doesn't have "time" or "otim" command */
6577           cps->sendTime = 0;
6578           return;
6579         }
6580         if (StrStr(message, "analyze")) {
6581             cps->analysisSupport = FALSE;
6582             cps->analyzing = FALSE;
6583             Reset(FALSE, TRUE);
6584             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6585             DisplayError(buf2, 0);
6586             return;
6587         }
6588         if (StrStr(message, "(no matching move)st")) {
6589           /* Special kludge for GNU Chess 4 only */
6590           cps->stKludge = TRUE;
6591           SendTimeControl(cps, movesPerSession, timeControl,
6592                           timeIncrement, appData.searchDepth,
6593                           searchTime);
6594           return;
6595         }
6596         if (StrStr(message, "(no matching move)sd")) {
6597           /* Special kludge for GNU Chess 4 only */
6598           cps->sdKludge = TRUE;
6599           SendTimeControl(cps, movesPerSession, timeControl,
6600                           timeIncrement, appData.searchDepth,
6601                           searchTime);
6602           return;
6603         }
6604         if (!StrStr(message, "llegal")) {
6605             return;
6606         }
6607         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6608             gameMode == IcsIdle) return;
6609         if (forwardMostMove <= backwardMostMove) return;
6610         if (pausing) PauseEvent();
6611       if(appData.forceIllegal) {
6612             // [HGM] illegal: machine refused move; force position after move into it
6613           SendToProgram("force\n", cps);
6614           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6615                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6616                 // when black is to move, while there might be nothing on a2 or black
6617                 // might already have the move. So send the board as if white has the move.
6618                 // But first we must change the stm of the engine, as it refused the last move
6619                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6620                 if(WhiteOnMove(forwardMostMove)) {
6621                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6622                     SendBoard(cps, forwardMostMove); // kludgeless board
6623                 } else {
6624                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6625                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6626                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6627                 }
6628           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6629             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6630                  gameMode == TwoMachinesPlay)
6631               SendToProgram("go\n", cps);
6632             return;
6633       } else
6634         if (gameMode == PlayFromGameFile) {
6635             /* Stop reading this game file */
6636             gameMode = EditGame;
6637             ModeHighlight();
6638         }
6639         currentMove = --forwardMostMove;
6640         DisplayMove(currentMove-1); /* before DisplayMoveError */
6641         SwitchClocks();
6642         DisplayBothClocks();
6643         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6644                 parseList[currentMove], cps->which);
6645         DisplayMoveError(buf1);
6646         DrawPosition(FALSE, boards[currentMove]);
6647
6648         /* [HGM] illegal-move claim should forfeit game when Xboard */
6649         /* only passes fully legal moves                            */
6650         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6651             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6652                                 "False illegal-move claim", GE_XBOARD );
6653         }
6654         return;
6655     }
6656     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6657         /* Program has a broken "time" command that
6658            outputs a string not ending in newline.
6659            Don't use it. */
6660         cps->sendTime = 0;
6661     }
6662     
6663     /*
6664      * If chess program startup fails, exit with an error message.
6665      * Attempts to recover here are futile.
6666      */
6667     if ((StrStr(message, "unknown host") != NULL)
6668         || (StrStr(message, "No remote directory") != NULL)
6669         || (StrStr(message, "not found") != NULL)
6670         || (StrStr(message, "No such file") != NULL)
6671         || (StrStr(message, "can't alloc") != NULL)
6672         || (StrStr(message, "Permission denied") != NULL)) {
6673
6674         cps->maybeThinking = FALSE;
6675         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6676                 cps->which, cps->program, cps->host, message);
6677         RemoveInputSource(cps->isr);
6678         DisplayFatalError(buf1, 0, 1);
6679         return;
6680     }
6681     
6682     /* 
6683      * Look for hint output
6684      */
6685     if (sscanf(message, "Hint: %s", buf1) == 1) {
6686         if (cps == &first && hintRequested) {
6687             hintRequested = FALSE;
6688             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6689                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6690                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6691                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6692                                     fromY, fromX, toY, toX, promoChar, buf1);
6693                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6694                 DisplayInformation(buf2);
6695             } else {
6696                 /* Hint move could not be parsed!? */
6697               snprintf(buf2, sizeof(buf2),
6698                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6699                         buf1, cps->which);
6700                 DisplayError(buf2, 0);
6701             }
6702         } else {
6703             strcpy(lastHint, buf1);
6704         }
6705         return;
6706     }
6707
6708     /*
6709      * Ignore other messages if game is not in progress
6710      */
6711     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6712         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6713
6714     /*
6715      * look for win, lose, draw, or draw offer
6716      */
6717     if (strncmp(message, "1-0", 3) == 0) {
6718         char *p, *q, *r = "";
6719         p = strchr(message, '{');
6720         if (p) {
6721             q = strchr(p, '}');
6722             if (q) {
6723                 *q = NULLCHAR;
6724                 r = p + 1;
6725             }
6726         }
6727         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6728         return;
6729     } else if (strncmp(message, "0-1", 3) == 0) {
6730         char *p, *q, *r = "";
6731         p = strchr(message, '{');
6732         if (p) {
6733             q = strchr(p, '}');
6734             if (q) {
6735                 *q = NULLCHAR;
6736                 r = p + 1;
6737             }
6738         }
6739         /* Kludge for Arasan 4.1 bug */
6740         if (strcmp(r, "Black resigns") == 0) {
6741             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6742             return;
6743         }
6744         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6745         return;
6746     } else if (strncmp(message, "1/2", 3) == 0) {
6747         char *p, *q, *r = "";
6748         p = strchr(message, '{');
6749         if (p) {
6750             q = strchr(p, '}');
6751             if (q) {
6752                 *q = NULLCHAR;
6753                 r = p + 1;
6754             }
6755         }
6756             
6757         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6758         return;
6759
6760     } else if (strncmp(message, "White resign", 12) == 0) {
6761         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6762         return;
6763     } else if (strncmp(message, "Black resign", 12) == 0) {
6764         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6765         return;
6766     } else if (strncmp(message, "White matches", 13) == 0 ||
6767                strncmp(message, "Black matches", 13) == 0   ) {
6768         /* [HGM] ignore GNUShogi noises */
6769         return;
6770     } else if (strncmp(message, "White", 5) == 0 &&
6771                message[5] != '(' &&
6772                StrStr(message, "Black") == NULL) {
6773         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6774         return;
6775     } else if (strncmp(message, "Black", 5) == 0 &&
6776                message[5] != '(') {
6777         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6778         return;
6779     } else if (strcmp(message, "resign") == 0 ||
6780                strcmp(message, "computer resigns") == 0) {
6781         switch (gameMode) {
6782           case MachinePlaysBlack:
6783           case IcsPlayingBlack:
6784             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6785             break;
6786           case MachinePlaysWhite:
6787           case IcsPlayingWhite:
6788             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6789             break;
6790           case TwoMachinesPlay:
6791             if (cps->twoMachinesColor[0] == 'w')
6792               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6793             else
6794               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6795             break;
6796           default:
6797             /* can't happen */
6798             break;
6799         }
6800         return;
6801     } else if (strncmp(message, "opponent mates", 14) == 0) {
6802         switch (gameMode) {
6803           case MachinePlaysBlack:
6804           case IcsPlayingBlack:
6805             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6806             break;
6807           case MachinePlaysWhite:
6808           case IcsPlayingWhite:
6809             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6810             break;
6811           case TwoMachinesPlay:
6812             if (cps->twoMachinesColor[0] == 'w')
6813               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6814             else
6815               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6816             break;
6817           default:
6818             /* can't happen */
6819             break;
6820         }
6821         return;
6822     } else if (strncmp(message, "computer mates", 14) == 0) {
6823         switch (gameMode) {
6824           case MachinePlaysBlack:
6825           case IcsPlayingBlack:
6826             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6827             break;
6828           case MachinePlaysWhite:
6829           case IcsPlayingWhite:
6830             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6831             break;
6832           case TwoMachinesPlay:
6833             if (cps->twoMachinesColor[0] == 'w')
6834               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6835             else
6836               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6837             break;
6838           default:
6839             /* can't happen */
6840             break;
6841         }
6842         return;
6843     } else if (strncmp(message, "checkmate", 9) == 0) {
6844         if (WhiteOnMove(forwardMostMove)) {
6845             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6846         } else {
6847             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6848         }
6849         return;
6850     } else if (strstr(message, "Draw") != NULL ||
6851                strstr(message, "game is a draw") != NULL) {
6852         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6853         return;
6854     } else if (strstr(message, "offer") != NULL &&
6855                strstr(message, "draw") != NULL) {
6856 #if ZIPPY
6857         if (appData.zippyPlay && first.initDone) {
6858             /* Relay offer to ICS */
6859             SendToICS(ics_prefix);
6860             SendToICS("draw\n");
6861         }
6862 #endif
6863         cps->offeredDraw = 2; /* valid until this engine moves twice */
6864         if (gameMode == TwoMachinesPlay) {
6865             if (cps->other->offeredDraw) {
6866                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6867             /* [HGM] in two-machine mode we delay relaying draw offer      */
6868             /* until after we also have move, to see if it is really claim */
6869             }
6870         } else if (gameMode == MachinePlaysWhite ||
6871                    gameMode == MachinePlaysBlack) {
6872           if (userOfferedDraw) {
6873             DisplayInformation(_("Machine accepts your draw offer"));
6874             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6875           } else {
6876             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6877           }
6878         }
6879     }
6880
6881     
6882     /*
6883      * Look for thinking output
6884      */
6885     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6886           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6887                                 ) {
6888         int plylev, mvleft, mvtot, curscore, time;
6889         char mvname[MOVE_LEN];
6890         u64 nodes; // [DM]
6891         char plyext;
6892         int ignore = FALSE;
6893         int prefixHint = FALSE;
6894         mvname[0] = NULLCHAR;
6895
6896         switch (gameMode) {
6897           case MachinePlaysBlack:
6898           case IcsPlayingBlack:
6899             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6900             break;
6901           case MachinePlaysWhite:
6902           case IcsPlayingWhite:
6903             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6904             break;
6905           case AnalyzeMode:
6906           case AnalyzeFile:
6907             break;
6908           case IcsObserving: /* [DM] icsEngineAnalyze */
6909             if (!appData.icsEngineAnalyze) ignore = TRUE;
6910             break;
6911           case TwoMachinesPlay:
6912             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6913                 ignore = TRUE;
6914             }
6915             break;
6916           default:
6917             ignore = TRUE;
6918             break;
6919         }
6920
6921         if (!ignore) {
6922             buf1[0] = NULLCHAR;
6923             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6924                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6925
6926                 if (plyext != ' ' && plyext != '\t') {
6927                     time *= 100;
6928                 }
6929
6930                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6931                 if( cps->scoreIsAbsolute && 
6932                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6933                 {
6934                     curscore = -curscore;
6935                 }
6936
6937
6938                 programStats.depth = plylev;
6939                 programStats.nodes = nodes;
6940                 programStats.time = time;
6941                 programStats.score = curscore;
6942                 programStats.got_only_move = 0;
6943
6944                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6945                         int ticklen;
6946
6947                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6948                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6949                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6950                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6951                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6952                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6953                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6954                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6955                 }
6956
6957                 /* Buffer overflow protection */
6958                 if (buf1[0] != NULLCHAR) {
6959                     if (strlen(buf1) >= sizeof(programStats.movelist)
6960                         && appData.debugMode) {
6961                         fprintf(debugFP,
6962                                 "PV is too long; using the first %d bytes.\n",
6963                                 sizeof(programStats.movelist) - 1);
6964                     }
6965
6966                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6967                 } else {
6968                     sprintf(programStats.movelist, " no PV\n");
6969                 }
6970
6971                 if (programStats.seen_stat) {
6972                     programStats.ok_to_send = 1;
6973                 }
6974
6975                 if (strchr(programStats.movelist, '(') != NULL) {
6976                     programStats.line_is_book = 1;
6977                     programStats.nr_moves = 0;
6978                     programStats.moves_left = 0;
6979                 } else {
6980                     programStats.line_is_book = 0;
6981                 }
6982
6983                 SendProgramStatsToFrontend( cps, &programStats );
6984
6985                 /* 
6986                     [AS] Protect the thinkOutput buffer from overflow... this
6987                     is only useful if buf1 hasn't overflowed first!
6988                 */
6989                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6990                         plylev, 
6991                         (gameMode == TwoMachinesPlay ?
6992                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6993                         ((double) curscore) / 100.0,
6994                         prefixHint ? lastHint : "",
6995                         prefixHint ? " " : "" );
6996
6997                 if( buf1[0] != NULLCHAR ) {
6998                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6999
7000                     if( strlen(buf1) > max_len ) {
7001                         if( appData.debugMode) {
7002                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7003                         }
7004                         buf1[max_len+1] = '\0';
7005                     }
7006
7007                     strcat( thinkOutput, buf1 );
7008                 }
7009
7010                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7011                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7012                     DisplayMove(currentMove - 1);
7013                 }
7014                 return;
7015
7016             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7017                 /* crafty (9.25+) says "(only move) <move>"
7018                  * if there is only 1 legal move
7019                  */
7020                 sscanf(p, "(only move) %s", buf1);
7021                 sprintf(thinkOutput, "%s (only move)", buf1);
7022                 sprintf(programStats.movelist, "%s (only move)", buf1);
7023                 programStats.depth = 1;
7024                 programStats.nr_moves = 1;
7025                 programStats.moves_left = 1;
7026                 programStats.nodes = 1;
7027                 programStats.time = 1;
7028                 programStats.got_only_move = 1;
7029
7030                 /* Not really, but we also use this member to
7031                    mean "line isn't going to change" (Crafty
7032                    isn't searching, so stats won't change) */
7033                 programStats.line_is_book = 1;
7034
7035                 SendProgramStatsToFrontend( cps, &programStats );
7036                 
7037                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7038                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7039                     DisplayMove(currentMove - 1);
7040                 }
7041                 return;
7042             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7043                               &time, &nodes, &plylev, &mvleft,
7044                               &mvtot, mvname) >= 5) {
7045                 /* The stat01: line is from Crafty (9.29+) in response
7046                    to the "." command */
7047                 programStats.seen_stat = 1;
7048                 cps->maybeThinking = TRUE;
7049
7050                 if (programStats.got_only_move || !appData.periodicUpdates)
7051                   return;
7052
7053                 programStats.depth = plylev;
7054                 programStats.time = time;
7055                 programStats.nodes = nodes;
7056                 programStats.moves_left = mvleft;
7057                 programStats.nr_moves = mvtot;
7058                 strcpy(programStats.move_name, mvname);
7059                 programStats.ok_to_send = 1;
7060                 programStats.movelist[0] = '\0';
7061
7062                 SendProgramStatsToFrontend( cps, &programStats );
7063
7064                 return;
7065
7066             } else if (strncmp(message,"++",2) == 0) {
7067                 /* Crafty 9.29+ outputs this */
7068                 programStats.got_fail = 2;
7069                 return;
7070
7071             } else if (strncmp(message,"--",2) == 0) {
7072                 /* Crafty 9.29+ outputs this */
7073                 programStats.got_fail = 1;
7074                 return;
7075
7076             } else if (thinkOutput[0] != NULLCHAR &&
7077                        strncmp(message, "    ", 4) == 0) {
7078                 unsigned message_len;
7079
7080                 p = message;
7081                 while (*p && *p == ' ') p++;
7082
7083                 message_len = strlen( p );
7084
7085                 /* [AS] Avoid buffer overflow */
7086                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7087                     strcat(thinkOutput, " ");
7088                     strcat(thinkOutput, p);
7089                 }
7090
7091                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7092                     strcat(programStats.movelist, " ");
7093                     strcat(programStats.movelist, p);
7094                 }
7095
7096                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7097                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7098                     DisplayMove(currentMove - 1);
7099                 }
7100                 return;
7101             }
7102         }
7103         else {
7104             buf1[0] = NULLCHAR;
7105
7106             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7107                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7108             {
7109                 ChessProgramStats cpstats;
7110
7111                 if (plyext != ' ' && plyext != '\t') {
7112                     time *= 100;
7113                 }
7114
7115                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7116                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7117                     curscore = -curscore;
7118                 }
7119
7120                 cpstats.depth = plylev;
7121                 cpstats.nodes = nodes;
7122                 cpstats.time = time;
7123                 cpstats.score = curscore;
7124                 cpstats.got_only_move = 0;
7125                 cpstats.movelist[0] = '\0';
7126
7127                 if (buf1[0] != NULLCHAR) {
7128                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7129                 }
7130
7131                 cpstats.ok_to_send = 0;
7132                 cpstats.line_is_book = 0;
7133                 cpstats.nr_moves = 0;
7134                 cpstats.moves_left = 0;
7135
7136                 SendProgramStatsToFrontend( cps, &cpstats );
7137             }
7138         }
7139     }
7140 }
7141
7142
7143 /* Parse a game score from the character string "game", and
7144    record it as the history of the current game.  The game
7145    score is NOT assumed to start from the standard position. 
7146    The display is not updated in any way.
7147    */
7148 void
7149 ParseGameHistory(game)
7150      char *game;
7151 {
7152     ChessMove moveType;
7153     int fromX, fromY, toX, toY, boardIndex;
7154     char promoChar;
7155     char *p, *q;
7156     char buf[MSG_SIZ];
7157
7158     if (appData.debugMode)
7159       fprintf(debugFP, "Parsing game history: %s\n", game);
7160
7161     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7162     gameInfo.site = StrSave(appData.icsHost);
7163     gameInfo.date = PGNDate();
7164     gameInfo.round = StrSave("-");
7165
7166     /* Parse out names of players */
7167     while (*game == ' ') game++;
7168     p = buf;
7169     while (*game != ' ') *p++ = *game++;
7170     *p = NULLCHAR;
7171     gameInfo.white = StrSave(buf);
7172     while (*game == ' ') game++;
7173     p = buf;
7174     while (*game != ' ' && *game != '\n') *p++ = *game++;
7175     *p = NULLCHAR;
7176     gameInfo.black = StrSave(buf);
7177
7178     /* Parse moves */
7179     boardIndex = blackPlaysFirst ? 1 : 0;
7180     yynewstr(game);
7181     for (;;) {
7182         yyboardindex = boardIndex;
7183         moveType = (ChessMove) yylex();
7184         switch (moveType) {
7185           case IllegalMove:             /* maybe suicide chess, etc. */
7186   if (appData.debugMode) {
7187     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7188     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7189     setbuf(debugFP, NULL);
7190   }
7191           case WhitePromotionChancellor:
7192           case BlackPromotionChancellor:
7193           case WhitePromotionArchbishop:
7194           case BlackPromotionArchbishop:
7195           case WhitePromotionQueen:
7196           case BlackPromotionQueen:
7197           case WhitePromotionRook:
7198           case BlackPromotionRook:
7199           case WhitePromotionBishop:
7200           case BlackPromotionBishop:
7201           case WhitePromotionKnight:
7202           case BlackPromotionKnight:
7203           case WhitePromotionKing:
7204           case BlackPromotionKing:
7205           case NormalMove:
7206           case WhiteCapturesEnPassant:
7207           case BlackCapturesEnPassant:
7208           case WhiteKingSideCastle:
7209           case WhiteQueenSideCastle:
7210           case BlackKingSideCastle:
7211           case BlackQueenSideCastle:
7212           case WhiteKingSideCastleWild:
7213           case WhiteQueenSideCastleWild:
7214           case BlackKingSideCastleWild:
7215           case BlackQueenSideCastleWild:
7216           /* PUSH Fabien */
7217           case WhiteHSideCastleFR:
7218           case WhiteASideCastleFR:
7219           case BlackHSideCastleFR:
7220           case BlackASideCastleFR:
7221           /* POP Fabien */
7222             fromX = currentMoveString[0] - AAA;
7223             fromY = currentMoveString[1] - ONE;
7224             toX = currentMoveString[2] - AAA;
7225             toY = currentMoveString[3] - ONE;
7226             promoChar = currentMoveString[4];
7227             break;
7228           case WhiteDrop:
7229           case BlackDrop:
7230             fromX = moveType == WhiteDrop ?
7231               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7232             (int) CharToPiece(ToLower(currentMoveString[0]));
7233             fromY = DROP_RANK;
7234             toX = currentMoveString[2] - AAA;
7235             toY = currentMoveString[3] - ONE;
7236             promoChar = NULLCHAR;
7237             break;
7238           case AmbiguousMove:
7239             /* bug? */
7240             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7241   if (appData.debugMode) {
7242     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7243     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7244     setbuf(debugFP, NULL);
7245   }
7246             DisplayError(buf, 0);
7247             return;
7248           case ImpossibleMove:
7249             /* bug? */
7250             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7251   if (appData.debugMode) {
7252     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7253     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7254     setbuf(debugFP, NULL);
7255   }
7256             DisplayError(buf, 0);
7257             return;
7258           case (ChessMove) 0:   /* end of file */
7259             if (boardIndex < backwardMostMove) {
7260                 /* Oops, gap.  How did that happen? */
7261                 DisplayError(_("Gap in move list"), 0);
7262                 return;
7263             }
7264             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7265             if (boardIndex > forwardMostMove) {
7266                 forwardMostMove = boardIndex;
7267             }
7268             return;
7269           case ElapsedTime:
7270             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7271                 strcat(parseList[boardIndex-1], " ");
7272                 strcat(parseList[boardIndex-1], yy_text);
7273             }
7274             continue;
7275           case Comment:
7276           case PGNTag:
7277           case NAG:
7278           default:
7279             /* ignore */
7280             continue;
7281           case WhiteWins:
7282           case BlackWins:
7283           case GameIsDrawn:
7284           case GameUnfinished:
7285             if (gameMode == IcsExamining) {
7286                 if (boardIndex < backwardMostMove) {
7287                     /* Oops, gap.  How did that happen? */
7288                     return;
7289                 }
7290                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7291                 return;
7292             }
7293             gameInfo.result = moveType;
7294             p = strchr(yy_text, '{');
7295             if (p == NULL) p = strchr(yy_text, '(');
7296             if (p == NULL) {
7297                 p = yy_text;
7298                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7299             } else {
7300                 q = strchr(p, *p == '{' ? '}' : ')');
7301                 if (q != NULL) *q = NULLCHAR;
7302                 p++;
7303             }
7304             gameInfo.resultDetails = StrSave(p);
7305             continue;
7306         }
7307         if (boardIndex >= forwardMostMove &&
7308             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7309             backwardMostMove = blackPlaysFirst ? 1 : 0;
7310             return;
7311         }
7312         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7313                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7314                                  parseList[boardIndex]);
7315         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7316         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7317         /* currentMoveString is set as a side-effect of yylex */
7318         strcpy(moveList[boardIndex], currentMoveString);
7319         strcat(moveList[boardIndex], "\n");
7320         boardIndex++;
7321         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7322                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7323         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7324                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7325           case MT_NONE:
7326           case MT_STALEMATE:
7327           default:
7328             break;
7329           case MT_CHECK:
7330             if(gameInfo.variant != VariantShogi)
7331                 strcat(parseList[boardIndex - 1], "+");
7332             break;
7333           case MT_CHECKMATE:
7334           case MT_STAINMATE:
7335             strcat(parseList[boardIndex - 1], "#");
7336             break;
7337         }
7338     }
7339 }
7340
7341
7342 /* Apply a move to the given board  */
7343 void
7344 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7345      int fromX, fromY, toX, toY;
7346      int promoChar;
7347      Board board;
7348      char *castling;
7349      char *ep;
7350 {
7351   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7352
7353     /* [HGM] compute & store e.p. status and castling rights for new position */
7354     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7355     { int i;
7356
7357       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7358       oldEP = *ep;
7359       *ep = EP_NONE;
7360
7361       if( board[toY][toX] != EmptySquare ) 
7362            *ep = EP_CAPTURE;  
7363
7364       if( board[fromY][fromX] == WhitePawn ) {
7365            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7366                *ep = EP_PAWN_MOVE;
7367            if( toY-fromY==2) {
7368                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7369                         gameInfo.variant != VariantBerolina || toX < fromX)
7370                       *ep = toX | berolina;
7371                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7372                         gameInfo.variant != VariantBerolina || toX > fromX) 
7373                       *ep = toX;
7374            }
7375       } else 
7376       if( board[fromY][fromX] == BlackPawn ) {
7377            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7378                *ep = EP_PAWN_MOVE; 
7379            if( toY-fromY== -2) {
7380                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7381                         gameInfo.variant != VariantBerolina || toX < fromX)
7382                       *ep = toX | berolina;
7383                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7384                         gameInfo.variant != VariantBerolina || toX > fromX) 
7385                       *ep = toX;
7386            }
7387        }
7388
7389        for(i=0; i<nrCastlingRights; i++) {
7390            if(castling[i] == fromX && castlingRank[i] == fromY ||
7391               castling[i] == toX   && castlingRank[i] == toY   
7392              ) castling[i] = -1; // revoke for moved or captured piece
7393        }
7394
7395     }
7396
7397   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7398   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7399        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7400          
7401   if (fromX == toX && fromY == toY) return;
7402
7403   if (fromY == DROP_RANK) {
7404         /* must be first */
7405         piece = board[toY][toX] = (ChessSquare) fromX;
7406   } else {
7407      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7408      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7409      if(gameInfo.variant == VariantKnightmate)
7410          king += (int) WhiteUnicorn - (int) WhiteKing;
7411
7412     /* Code added by Tord: */
7413     /* FRC castling assumed when king captures friendly rook. */
7414     if (board[fromY][fromX] == WhiteKing &&
7415              board[toY][toX] == WhiteRook) {
7416       board[fromY][fromX] = EmptySquare;
7417       board[toY][toX] = EmptySquare;
7418       if(toX > fromX) {
7419         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7420       } else {
7421         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7422       }
7423     } else if (board[fromY][fromX] == BlackKing &&
7424                board[toY][toX] == BlackRook) {
7425       board[fromY][fromX] = EmptySquare;
7426       board[toY][toX] = EmptySquare;
7427       if(toX > fromX) {
7428         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7429       } else {
7430         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7431       }
7432     /* End of code added by Tord */
7433
7434     } else if (board[fromY][fromX] == king
7435         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7436         && toY == fromY && toX > fromX+1) {
7437         board[fromY][fromX] = EmptySquare;
7438         board[toY][toX] = king;
7439         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7440         board[fromY][BOARD_RGHT-1] = EmptySquare;
7441     } else if (board[fromY][fromX] == king
7442         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7443                && toY == fromY && toX < fromX-1) {
7444         board[fromY][fromX] = EmptySquare;
7445         board[toY][toX] = king;
7446         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7447         board[fromY][BOARD_LEFT] = EmptySquare;
7448     } else if (board[fromY][fromX] == WhitePawn
7449                && toY == BOARD_HEIGHT-1
7450                && gameInfo.variant != VariantXiangqi
7451                ) {
7452         /* white pawn promotion */
7453         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7454         if (board[toY][toX] == EmptySquare) {
7455             board[toY][toX] = WhiteQueen;
7456         }
7457         if(gameInfo.variant==VariantBughouse ||
7458            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7459             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7460         board[fromY][fromX] = EmptySquare;
7461     } else if ((fromY == BOARD_HEIGHT-4)
7462                && (toX != fromX)
7463                && gameInfo.variant != VariantXiangqi
7464                && gameInfo.variant != VariantBerolina
7465                && (board[fromY][fromX] == WhitePawn)
7466                && (board[toY][toX] == EmptySquare)) {
7467         board[fromY][fromX] = EmptySquare;
7468         board[toY][toX] = WhitePawn;
7469         captured = board[toY - 1][toX];
7470         board[toY - 1][toX] = EmptySquare;
7471     } else if ((fromY == BOARD_HEIGHT-4)
7472                && (toX == fromX)
7473                && gameInfo.variant == VariantBerolina
7474                && (board[fromY][fromX] == WhitePawn)
7475                && (board[toY][toX] == EmptySquare)) {
7476         board[fromY][fromX] = EmptySquare;
7477         board[toY][toX] = WhitePawn;
7478         if(oldEP & EP_BEROLIN_A) {
7479                 captured = board[fromY][fromX-1];
7480                 board[fromY][fromX-1] = EmptySquare;
7481         }else{  captured = board[fromY][fromX+1];
7482                 board[fromY][fromX+1] = EmptySquare;
7483         }
7484     } else if (board[fromY][fromX] == king
7485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7486                && toY == fromY && toX > fromX+1) {
7487         board[fromY][fromX] = EmptySquare;
7488         board[toY][toX] = king;
7489         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7490         board[fromY][BOARD_RGHT-1] = EmptySquare;
7491     } else if (board[fromY][fromX] == king
7492         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7493                && toY == fromY && toX < fromX-1) {
7494         board[fromY][fromX] = EmptySquare;
7495         board[toY][toX] = king;
7496         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7497         board[fromY][BOARD_LEFT] = EmptySquare;
7498     } else if (fromY == 7 && fromX == 3
7499                && board[fromY][fromX] == BlackKing
7500                && toY == 7 && toX == 5) {
7501         board[fromY][fromX] = EmptySquare;
7502         board[toY][toX] = BlackKing;
7503         board[fromY][7] = EmptySquare;
7504         board[toY][4] = BlackRook;
7505     } else if (fromY == 7 && fromX == 3
7506                && board[fromY][fromX] == BlackKing
7507                && toY == 7 && toX == 1) {
7508         board[fromY][fromX] = EmptySquare;
7509         board[toY][toX] = BlackKing;
7510         board[fromY][0] = EmptySquare;
7511         board[toY][2] = BlackRook;
7512     } else if (board[fromY][fromX] == BlackPawn
7513                && toY == 0
7514                && gameInfo.variant != VariantXiangqi
7515                ) {
7516         /* black pawn promotion */
7517         board[0][toX] = CharToPiece(ToLower(promoChar));
7518         if (board[0][toX] == EmptySquare) {
7519             board[0][toX] = BlackQueen;
7520         }
7521         if(gameInfo.variant==VariantBughouse ||
7522            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7523             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7524         board[fromY][fromX] = EmptySquare;
7525     } else if ((fromY == 3)
7526                && (toX != fromX)
7527                && gameInfo.variant != VariantXiangqi
7528                && gameInfo.variant != VariantBerolina
7529                && (board[fromY][fromX] == BlackPawn)
7530                && (board[toY][toX] == EmptySquare)) {
7531         board[fromY][fromX] = EmptySquare;
7532         board[toY][toX] = BlackPawn;
7533         captured = board[toY + 1][toX];
7534         board[toY + 1][toX] = EmptySquare;
7535     } else if ((fromY == 3)
7536                && (toX == fromX)
7537                && gameInfo.variant == VariantBerolina
7538                && (board[fromY][fromX] == BlackPawn)
7539                && (board[toY][toX] == EmptySquare)) {
7540         board[fromY][fromX] = EmptySquare;
7541         board[toY][toX] = BlackPawn;
7542         if(oldEP & EP_BEROLIN_A) {
7543                 captured = board[fromY][fromX-1];
7544                 board[fromY][fromX-1] = EmptySquare;
7545         }else{  captured = board[fromY][fromX+1];
7546                 board[fromY][fromX+1] = EmptySquare;
7547         }
7548     } else {
7549         board[toY][toX] = board[fromY][fromX];
7550         board[fromY][fromX] = EmptySquare;
7551     }
7552
7553     /* [HGM] now we promote for Shogi, if needed */
7554     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7555         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7556   }
7557
7558     if (gameInfo.holdingsWidth != 0) {
7559
7560       /* !!A lot more code needs to be written to support holdings  */
7561       /* [HGM] OK, so I have written it. Holdings are stored in the */
7562       /* penultimate board files, so they are automaticlly stored   */
7563       /* in the game history.                                       */
7564       if (fromY == DROP_RANK) {
7565         /* Delete from holdings, by decreasing count */
7566         /* and erasing image if necessary            */
7567         p = (int) fromX;
7568         if(p < (int) BlackPawn) { /* white drop */
7569              p -= (int)WhitePawn;
7570                  p = PieceToNumber((ChessSquare)p);
7571              if(p >= gameInfo.holdingsSize) p = 0;
7572              if(--board[p][BOARD_WIDTH-2] <= 0)
7573                   board[p][BOARD_WIDTH-1] = EmptySquare;
7574              if((int)board[p][BOARD_WIDTH-2] < 0)
7575                         board[p][BOARD_WIDTH-2] = 0;
7576         } else {                  /* black drop */
7577              p -= (int)BlackPawn;
7578                  p = PieceToNumber((ChessSquare)p);
7579              if(p >= gameInfo.holdingsSize) p = 0;
7580              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7581                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7582              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7583                         board[BOARD_HEIGHT-1-p][1] = 0;
7584         }
7585       }
7586       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7587           && gameInfo.variant != VariantBughouse        ) {
7588         /* [HGM] holdings: Add to holdings, if holdings exist */
7589         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7590                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7591                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7592         }
7593         p = (int) captured;
7594         if (p >= (int) BlackPawn) {
7595           p -= (int)BlackPawn;
7596           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7597                   /* in Shogi restore piece to its original  first */
7598                   captured = (ChessSquare) (DEMOTED captured);
7599                   p = DEMOTED p;
7600           }
7601           p = PieceToNumber((ChessSquare)p);
7602           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7603           board[p][BOARD_WIDTH-2]++;
7604           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7605         } else {
7606           p -= (int)WhitePawn;
7607           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7608                   captured = (ChessSquare) (DEMOTED captured);
7609                   p = DEMOTED p;
7610           }
7611           p = PieceToNumber((ChessSquare)p);
7612           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7613           board[BOARD_HEIGHT-1-p][1]++;
7614           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7615         }
7616       }
7617     } else if (gameInfo.variant == VariantAtomic) {
7618       if (captured != EmptySquare) {
7619         int y, x;
7620         for (y = toY-1; y <= toY+1; y++) {
7621           for (x = toX-1; x <= toX+1; x++) {
7622             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7623                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7624               board[y][x] = EmptySquare;
7625             }
7626           }
7627         }
7628         board[toY][toX] = EmptySquare;
7629       }
7630     }
7631     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7632         /* [HGM] Shogi promotions */
7633         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7634     }
7635
7636     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7637                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7638         // [HGM] superchess: take promotion piece out of holdings
7639         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7640         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7641             if(!--board[k][BOARD_WIDTH-2])
7642                 board[k][BOARD_WIDTH-1] = EmptySquare;
7643         } else {
7644             if(!--board[BOARD_HEIGHT-1-k][1])
7645                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7646         }
7647     }
7648
7649 }
7650
7651 /* Updates forwardMostMove */
7652 void
7653 MakeMove(fromX, fromY, toX, toY, promoChar)
7654      int fromX, fromY, toX, toY;
7655      int promoChar;
7656 {
7657 //    forwardMostMove++; // [HGM] bare: moved downstream
7658
7659     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7660         int timeLeft; static int lastLoadFlag=0; int king, piece;
7661         piece = boards[forwardMostMove][fromY][fromX];
7662         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7663         if(gameInfo.variant == VariantKnightmate)
7664             king += (int) WhiteUnicorn - (int) WhiteKing;
7665         if(forwardMostMove == 0) {
7666             if(blackPlaysFirst) 
7667                 fprintf(serverMoves, "%s;", second.tidy);
7668             fprintf(serverMoves, "%s;", first.tidy);
7669             if(!blackPlaysFirst) 
7670                 fprintf(serverMoves, "%s;", second.tidy);
7671         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7672         lastLoadFlag = loadFlag;
7673         // print base move
7674         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7675         // print castling suffix
7676         if( toY == fromY && piece == king ) {
7677             if(toX-fromX > 1)
7678                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7679             if(fromX-toX >1)
7680                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7681         }
7682         // e.p. suffix
7683         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7684              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7685              boards[forwardMostMove][toY][toX] == EmptySquare
7686              && fromX != toX )
7687                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7688         // promotion suffix
7689         if(promoChar != NULLCHAR)
7690                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7691         if(!loadFlag) {
7692             fprintf(serverMoves, "/%d/%d",
7693                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7694             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7695             else                      timeLeft = blackTimeRemaining/1000;
7696             fprintf(serverMoves, "/%d", timeLeft);
7697         }
7698         fflush(serverMoves);
7699     }
7700
7701     if (forwardMostMove+1 >= MAX_MOVES) {
7702       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7703                         0, 1);
7704       return;
7705     }
7706     if (commentList[forwardMostMove+1] != NULL) {
7707         free(commentList[forwardMostMove+1]);
7708         commentList[forwardMostMove+1] = NULL;
7709     }
7710     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7711     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7712     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7713                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7714     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7715     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7716     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7717     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7718     gameInfo.result = GameUnfinished;
7719     if (gameInfo.resultDetails != NULL) {
7720         free(gameInfo.resultDetails);
7721         gameInfo.resultDetails = NULL;
7722     }
7723     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7724                               moveList[forwardMostMove - 1]);
7725     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7726                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7727                              fromY, fromX, toY, toX, promoChar,
7728                              parseList[forwardMostMove - 1]);
7729     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7730                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7731                             castlingRights[forwardMostMove]) ) {
7732       case MT_NONE:
7733       case MT_STALEMATE:
7734       default:
7735         break;
7736       case MT_CHECK:
7737         if(gameInfo.variant != VariantShogi)
7738             strcat(parseList[forwardMostMove - 1], "+");
7739         break;
7740       case MT_CHECKMATE:
7741       case MT_STAINMATE:
7742         strcat(parseList[forwardMostMove - 1], "#");
7743         break;
7744     }
7745     if (appData.debugMode) {
7746         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7747     }
7748
7749 }
7750
7751 /* Updates currentMove if not pausing */
7752 void
7753 ShowMove(fromX, fromY, toX, toY)
7754 {
7755     int instant = (gameMode == PlayFromGameFile) ?
7756         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7757     if(appData.noGUI) return;
7758     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7759         if (!instant) {
7760             if (forwardMostMove == currentMove + 1) {
7761                 AnimateMove(boards[forwardMostMove - 1],
7762                             fromX, fromY, toX, toY);
7763             }
7764             if (appData.highlightLastMove) {
7765                 SetHighlights(fromX, fromY, toX, toY);
7766             }
7767         }
7768         currentMove = forwardMostMove;
7769     }
7770
7771     if (instant) return;
7772
7773     DisplayMove(currentMove - 1);
7774     DrawPosition(FALSE, boards[currentMove]);
7775     DisplayBothClocks();
7776     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7777 }
7778
7779 void SendEgtPath(ChessProgramState *cps)
7780 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7781         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7782
7783         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7784
7785         while(*p) {
7786             char c, *q = name+1, *r, *s;
7787
7788             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7789             while(*p && *p != ',') *q++ = *p++;
7790             *q++ = ':'; *q = 0;
7791             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7792                 strcmp(name, ",nalimov:") == 0 ) {
7793                 // take nalimov path from the menu-changeable option first, if it is defined
7794                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7795                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7796             } else
7797             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7798                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7799                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7800                 s = r = StrStr(s, ":") + 1; // beginning of path info
7801                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7802                 c = *r; *r = 0;             // temporarily null-terminate path info
7803                     *--q = 0;               // strip of trailig ':' from name
7804                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7805                 *r = c;
7806                 SendToProgram(buf,cps);     // send egtbpath command for this format
7807             }
7808             if(*p == ',') p++; // read away comma to position for next format name
7809         }
7810 }
7811
7812 void
7813 InitChessProgram(cps, setup)
7814      ChessProgramState *cps;
7815      int setup; /* [HGM] needed to setup FRC opening position */
7816 {
7817     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7818     if (appData.noChessProgram) return;
7819     hintRequested = FALSE;
7820     bookRequested = FALSE;
7821
7822     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7823     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7824     if(cps->memSize) { /* [HGM] memory */
7825         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7826         SendToProgram(buf, cps);
7827     }
7828     SendEgtPath(cps); /* [HGM] EGT */
7829     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7830         sprintf(buf, "cores %d\n", appData.smpCores);
7831         SendToProgram(buf, cps);
7832     }
7833
7834     SendToProgram(cps->initString, cps);
7835     if (gameInfo.variant != VariantNormal &&
7836         gameInfo.variant != VariantLoadable
7837         /* [HGM] also send variant if board size non-standard */
7838         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7839                                             ) {
7840       char *v = VariantName(gameInfo.variant);
7841       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7842         /* [HGM] in protocol 1 we have to assume all variants valid */
7843         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7844         DisplayFatalError(buf, 0, 1);
7845         return;
7846       }
7847
7848       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7849       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7850       if( gameInfo.variant == VariantXiangqi )
7851            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7852       if( gameInfo.variant == VariantShogi )
7853            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7854       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7855            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7856       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7857                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7858            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7859       if( gameInfo.variant == VariantCourier )
7860            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861       if( gameInfo.variant == VariantSuper )
7862            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7863       if( gameInfo.variant == VariantGreat )
7864            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865
7866       if(overruled) {
7867            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7868                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7869            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7870            if(StrStr(cps->variants, b) == NULL) { 
7871                // specific sized variant not known, check if general sizing allowed
7872                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7873                    if(StrStr(cps->variants, "boardsize") == NULL) {
7874                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7875                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7876                        DisplayFatalError(buf, 0, 1);
7877                        return;
7878                    }
7879                    /* [HGM] here we really should compare with the maximum supported board size */
7880                }
7881            }
7882       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7883       sprintf(buf, "variant %s\n", b);
7884       SendToProgram(buf, cps);
7885     }
7886     currentlyInitializedVariant = gameInfo.variant;
7887
7888     /* [HGM] send opening position in FRC to first engine */
7889     if(setup) {
7890           SendToProgram("force\n", cps);
7891           SendBoard(cps, 0);
7892           /* engine is now in force mode! Set flag to wake it up after first move. */
7893           setboardSpoiledMachineBlack = 1;
7894     }
7895
7896     if (cps->sendICS) {
7897       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7898       SendToProgram(buf, cps);
7899     }
7900     cps->maybeThinking = FALSE;
7901     cps->offeredDraw = 0;
7902     if (!appData.icsActive) {
7903         SendTimeControl(cps, movesPerSession, timeControl,
7904                         timeIncrement, appData.searchDepth,
7905                         searchTime);
7906     }
7907     if (appData.showThinking 
7908         // [HGM] thinking: four options require thinking output to be sent
7909         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7910                                 ) {
7911         SendToProgram("post\n", cps);
7912     }
7913     SendToProgram("hard\n", cps);
7914     if (!appData.ponderNextMove) {
7915         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7916            it without being sure what state we are in first.  "hard"
7917            is not a toggle, so that one is OK.
7918          */
7919         SendToProgram("easy\n", cps);
7920     }
7921     if (cps->usePing) {
7922       sprintf(buf, "ping %d\n", ++cps->lastPing);
7923       SendToProgram(buf, cps);
7924     }
7925     cps->initDone = TRUE;
7926 }   
7927
7928
7929 void
7930 StartChessProgram(cps)
7931      ChessProgramState *cps;
7932 {
7933     char buf[MSG_SIZ];
7934     int err;
7935
7936     if (appData.noChessProgram) return;
7937     cps->initDone = FALSE;
7938
7939     if (strcmp(cps->host, "localhost") == 0) {
7940         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7941     } else if (*appData.remoteShell == NULLCHAR) {
7942         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7943     } else {
7944         if (*appData.remoteUser == NULLCHAR) {
7945           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7946                     cps->program);
7947         } else {
7948           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7949                     cps->host, appData.remoteUser, cps->program);
7950         }
7951         err = StartChildProcess(buf, "", &cps->pr);
7952     }
7953     
7954     if (err != 0) {
7955         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7956         DisplayFatalError(buf, err, 1);
7957         cps->pr = NoProc;
7958         cps->isr = NULL;
7959         return;
7960     }
7961     
7962     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7963     if (cps->protocolVersion > 1) {
7964       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7965       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7966       cps->comboCnt = 0;  //                and values of combo boxes
7967       SendToProgram(buf, cps);
7968     } else {
7969       SendToProgram("xboard\n", cps);
7970     }
7971 }
7972
7973
7974 void
7975 TwoMachinesEventIfReady P((void))
7976 {
7977   if (first.lastPing != first.lastPong) {
7978     DisplayMessage("", _("Waiting for first chess program"));
7979     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7980     return;
7981   }
7982   if (second.lastPing != second.lastPong) {
7983     DisplayMessage("", _("Waiting for second chess program"));
7984     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7985     return;
7986   }
7987   ThawUI();
7988   TwoMachinesEvent();
7989 }
7990
7991 void
7992 NextMatchGame P((void))
7993 {
7994     int index; /* [HGM] autoinc: step load index during match */
7995     Reset(FALSE, TRUE);
7996     if (*appData.loadGameFile != NULLCHAR) {
7997         index = appData.loadGameIndex;
7998         if(index < 0) { // [HGM] autoinc
7999             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8000             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8001         } 
8002         LoadGameFromFile(appData.loadGameFile,
8003                          index,
8004                          appData.loadGameFile, FALSE);
8005     } else if (*appData.loadPositionFile != NULLCHAR) {
8006         index = appData.loadPositionIndex;
8007         if(index < 0) { // [HGM] autoinc
8008             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8009             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8010         } 
8011         LoadPositionFromFile(appData.loadPositionFile,
8012                              index,
8013                              appData.loadPositionFile);
8014     }
8015     TwoMachinesEventIfReady();
8016 }
8017
8018 void UserAdjudicationEvent( int result )
8019 {
8020     ChessMove gameResult = GameIsDrawn;
8021
8022     if( result > 0 ) {
8023         gameResult = WhiteWins;
8024     }
8025     else if( result < 0 ) {
8026         gameResult = BlackWins;
8027     }
8028
8029     if( gameMode == TwoMachinesPlay ) {
8030         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8031     }
8032 }
8033
8034
8035 // [HGM] save: calculate checksum of game to make games easily identifiable
8036 int StringCheckSum(char *s)
8037 {
8038         int i = 0;
8039         if(s==NULL) return 0;
8040         while(*s) i = i*259 + *s++;
8041         return i;
8042 }
8043
8044 int GameCheckSum()
8045 {
8046         int i, sum=0;
8047         for(i=backwardMostMove; i<forwardMostMove; i++) {
8048                 sum += pvInfoList[i].depth;
8049                 sum += StringCheckSum(parseList[i]);
8050                 sum += StringCheckSum(commentList[i]);
8051                 sum *= 261;
8052         }
8053         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8054         return sum + StringCheckSum(commentList[i]);
8055 } // end of save patch
8056
8057 void
8058 GameEnds(result, resultDetails, whosays)
8059      ChessMove result;
8060      char *resultDetails;
8061      int whosays;
8062 {
8063     GameMode nextGameMode;
8064     int isIcsGame;
8065     char buf[MSG_SIZ];
8066
8067     if(endingGame) return; /* [HGM] crash: forbid recursion */
8068     endingGame = 1;
8069
8070     if (appData.debugMode) {
8071       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8072               result, resultDetails ? resultDetails : "(null)", whosays);
8073     }
8074
8075     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8076         /* If we are playing on ICS, the server decides when the
8077            game is over, but the engine can offer to draw, claim 
8078            a draw, or resign. 
8079          */
8080 #if ZIPPY
8081         if (appData.zippyPlay && first.initDone) {
8082             if (result == GameIsDrawn) {
8083                 /* In case draw still needs to be claimed */
8084                 SendToICS(ics_prefix);
8085                 SendToICS("draw\n");
8086             } else if (StrCaseStr(resultDetails, "resign")) {
8087                 SendToICS(ics_prefix);
8088                 SendToICS("resign\n");
8089             }
8090         }
8091 #endif
8092         endingGame = 0; /* [HGM] crash */
8093         return;
8094     }
8095
8096     /* If we're loading the game from a file, stop */
8097     if (whosays == GE_FILE) {
8098       (void) StopLoadGameTimer();
8099       gameFileFP = NULL;
8100     }
8101
8102     /* Cancel draw offers */
8103     first.offeredDraw = second.offeredDraw = 0;
8104
8105     /* If this is an ICS game, only ICS can really say it's done;
8106        if not, anyone can. */
8107     isIcsGame = (gameMode == IcsPlayingWhite || 
8108                  gameMode == IcsPlayingBlack || 
8109                  gameMode == IcsObserving    || 
8110                  gameMode == IcsExamining);
8111
8112     if (!isIcsGame || whosays == GE_ICS) {
8113         /* OK -- not an ICS game, or ICS said it was done */
8114         StopClocks();
8115         if (!isIcsGame && !appData.noChessProgram) 
8116           SetUserThinkingEnables();
8117     
8118         /* [HGM] if a machine claims the game end we verify this claim */
8119         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8120             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8121                 char claimer;
8122                 ChessMove trueResult = (ChessMove) -1;
8123
8124                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8125                                             first.twoMachinesColor[0] :
8126                                             second.twoMachinesColor[0] ;
8127
8128                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8129                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8130                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8131                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8132                 } else
8133                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8134                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8135                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8136                 } else
8137                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8138                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8139                 }
8140
8141                 // now verify win claims, but not in drop games, as we don't understand those yet
8142                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8143                                                  || gameInfo.variant == VariantGreat) &&
8144                     (result == WhiteWins && claimer == 'w' ||
8145                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8146                       if (appData.debugMode) {
8147                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8148                                 result, epStatus[forwardMostMove], forwardMostMove);
8149                       }
8150                       if(result != trueResult) {
8151                               sprintf(buf, "False win claim: '%s'", resultDetails);
8152                               result = claimer == 'w' ? BlackWins : WhiteWins;
8153                               resultDetails = buf;
8154                       }
8155                 } else
8156                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8157                     && (forwardMostMove <= backwardMostMove ||
8158                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8159                         (claimer=='b')==(forwardMostMove&1))
8160                                                                                   ) {
8161                       /* [HGM] verify: draws that were not flagged are false claims */
8162                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8163                       result = claimer == 'w' ? BlackWins : WhiteWins;
8164                       resultDetails = buf;
8165                 }
8166                 /* (Claiming a loss is accepted no questions asked!) */
8167             }
8168             /* [HGM] bare: don't allow bare King to win */
8169             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8170                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8171                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8172                && result != GameIsDrawn)
8173             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8174                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8175                         int p = (int)boards[forwardMostMove][i][j] - color;
8176                         if(p >= 0 && p <= (int)WhiteKing) k++;
8177                 }
8178                 if (appData.debugMode) {
8179                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8180                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8181                 }
8182                 if(k <= 1) {
8183                         result = GameIsDrawn;
8184                         sprintf(buf, "%s but bare king", resultDetails);
8185                         resultDetails = buf;
8186                 }
8187             }
8188         }
8189
8190
8191         if(serverMoves != NULL && !loadFlag) { char c = '=';
8192             if(result==WhiteWins) c = '+';
8193             if(result==BlackWins) c = '-';
8194             if(resultDetails != NULL)
8195                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8196         }
8197         if (resultDetails != NULL) {
8198             gameInfo.result = result;
8199             gameInfo.resultDetails = StrSave(resultDetails);
8200
8201             /* display last move only if game was not loaded from file */
8202             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8203                 DisplayMove(currentMove - 1);
8204     
8205             if (forwardMostMove != 0) {
8206                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8207                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8208                                                                 ) {
8209                     if (*appData.saveGameFile != NULLCHAR) {
8210                         SaveGameToFile(appData.saveGameFile, TRUE);
8211                     } else if (appData.autoSaveGames) {
8212                         AutoSaveGame();
8213                     }
8214                     if (*appData.savePositionFile != NULLCHAR) {
8215                         SavePositionToFile(appData.savePositionFile);
8216                     }
8217                 }
8218             }
8219
8220             /* Tell program how game ended in case it is learning */
8221             /* [HGM] Moved this to after saving the PGN, just in case */
8222             /* engine died and we got here through time loss. In that */
8223             /* case we will get a fatal error writing the pipe, which */
8224             /* would otherwise lose us the PGN.                       */
8225             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8226             /* output during GameEnds should never be fatal anymore   */
8227             if (gameMode == MachinePlaysWhite ||
8228                 gameMode == MachinePlaysBlack ||
8229                 gameMode == TwoMachinesPlay ||
8230                 gameMode == IcsPlayingWhite ||
8231                 gameMode == IcsPlayingBlack ||
8232                 gameMode == BeginningOfGame) {
8233                 char buf[MSG_SIZ];
8234                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8235                         resultDetails);
8236                 if (first.pr != NoProc) {
8237                     SendToProgram(buf, &first);
8238                 }
8239                 if (second.pr != NoProc &&
8240                     gameMode == TwoMachinesPlay) {
8241                     SendToProgram(buf, &second);
8242                 }
8243             }
8244         }
8245
8246         if (appData.icsActive) {
8247             if (appData.quietPlay &&
8248                 (gameMode == IcsPlayingWhite ||
8249                  gameMode == IcsPlayingBlack)) {
8250                 SendToICS(ics_prefix);
8251                 SendToICS("set shout 1\n");
8252             }
8253             nextGameMode = IcsIdle;
8254             ics_user_moved = FALSE;
8255             /* clean up premove.  It's ugly when the game has ended and the
8256              * premove highlights are still on the board.
8257              */
8258             if (gotPremove) {
8259               gotPremove = FALSE;
8260               ClearPremoveHighlights();
8261               DrawPosition(FALSE, boards[currentMove]);
8262             }
8263             if (whosays == GE_ICS) {
8264                 switch (result) {
8265                 case WhiteWins:
8266                     if (gameMode == IcsPlayingWhite)
8267                         PlayIcsWinSound();
8268                     else if(gameMode == IcsPlayingBlack)
8269                         PlayIcsLossSound();
8270                     break;
8271                 case BlackWins:
8272                     if (gameMode == IcsPlayingBlack)
8273                         PlayIcsWinSound();
8274                     else if(gameMode == IcsPlayingWhite)
8275                         PlayIcsLossSound();
8276                     break;
8277                 case GameIsDrawn:
8278                     PlayIcsDrawSound();
8279                     break;
8280                 default:
8281                     PlayIcsUnfinishedSound();
8282                 }
8283             }
8284         } else if (gameMode == EditGame ||
8285                    gameMode == PlayFromGameFile || 
8286                    gameMode == AnalyzeMode || 
8287                    gameMode == AnalyzeFile) {
8288             nextGameMode = gameMode;
8289         } else {
8290             nextGameMode = EndOfGame;
8291         }
8292         pausing = FALSE;
8293         ModeHighlight();
8294     } else {
8295         nextGameMode = gameMode;
8296     }
8297
8298     if (appData.noChessProgram) {
8299         gameMode = nextGameMode;
8300         ModeHighlight();
8301         endingGame = 0; /* [HGM] crash */
8302         return;
8303     }
8304
8305     if (first.reuse) {
8306         /* Put first chess program into idle state */
8307         if (first.pr != NoProc &&
8308             (gameMode == MachinePlaysWhite ||
8309              gameMode == MachinePlaysBlack ||
8310              gameMode == TwoMachinesPlay ||
8311              gameMode == IcsPlayingWhite ||
8312              gameMode == IcsPlayingBlack ||
8313              gameMode == BeginningOfGame)) {
8314             SendToProgram("force\n", &first);
8315             if (first.usePing) {
8316               char buf[MSG_SIZ];
8317               sprintf(buf, "ping %d\n", ++first.lastPing);
8318               SendToProgram(buf, &first);
8319             }
8320         }
8321     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8322         /* Kill off first chess program */
8323         if (first.isr != NULL)
8324           RemoveInputSource(first.isr);
8325         first.isr = NULL;
8326     
8327         if (first.pr != NoProc) {
8328             ExitAnalyzeMode();
8329             DoSleep( appData.delayBeforeQuit );
8330             SendToProgram("quit\n", &first);
8331             DoSleep( appData.delayAfterQuit );
8332             DestroyChildProcess(first.pr, first.useSigterm);
8333         }
8334         first.pr = NoProc;
8335     }
8336     if (second.reuse) {
8337         /* Put second chess program into idle state */
8338         if (second.pr != NoProc &&
8339             gameMode == TwoMachinesPlay) {
8340             SendToProgram("force\n", &second);
8341             if (second.usePing) {
8342               char buf[MSG_SIZ];
8343               sprintf(buf, "ping %d\n", ++second.lastPing);
8344               SendToProgram(buf, &second);
8345             }
8346         }
8347     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8348         /* Kill off second chess program */
8349         if (second.isr != NULL)
8350           RemoveInputSource(second.isr);
8351         second.isr = NULL;
8352     
8353         if (second.pr != NoProc) {
8354             DoSleep( appData.delayBeforeQuit );
8355             SendToProgram("quit\n", &second);
8356             DoSleep( appData.delayAfterQuit );
8357             DestroyChildProcess(second.pr, second.useSigterm);
8358         }
8359         second.pr = NoProc;
8360     }
8361
8362     if (matchMode && gameMode == TwoMachinesPlay) {
8363         switch (result) {
8364         case WhiteWins:
8365           if (first.twoMachinesColor[0] == 'w') {
8366             first.matchWins++;
8367           } else {
8368             second.matchWins++;
8369           }
8370           break;
8371         case BlackWins:
8372           if (first.twoMachinesColor[0] == 'b') {
8373             first.matchWins++;
8374           } else {
8375             second.matchWins++;
8376           }
8377           break;
8378         default:
8379           break;
8380         }
8381         if (matchGame < appData.matchGames) {
8382             char *tmp;
8383             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8384                 tmp = first.twoMachinesColor;
8385                 first.twoMachinesColor = second.twoMachinesColor;
8386                 second.twoMachinesColor = tmp;
8387             }
8388             gameMode = nextGameMode;
8389             matchGame++;
8390             if(appData.matchPause>10000 || appData.matchPause<10)
8391                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8392             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8393             endingGame = 0; /* [HGM] crash */
8394             return;
8395         } else {
8396             char buf[MSG_SIZ];
8397             gameMode = nextGameMode;
8398             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8399                     first.tidy, second.tidy,
8400                     first.matchWins, second.matchWins,
8401                     appData.matchGames - (first.matchWins + second.matchWins));
8402             DisplayFatalError(buf, 0, 0);
8403         }
8404     }
8405     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8406         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8407       ExitAnalyzeMode();
8408     gameMode = nextGameMode;
8409     ModeHighlight();
8410     endingGame = 0;  /* [HGM] crash */
8411 }
8412
8413 /* Assumes program was just initialized (initString sent).
8414    Leaves program in force mode. */
8415 void
8416 FeedMovesToProgram(cps, upto) 
8417      ChessProgramState *cps;
8418      int upto;
8419 {
8420     int i;
8421     
8422     if (appData.debugMode)
8423       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8424               startedFromSetupPosition ? "position and " : "",
8425               backwardMostMove, upto, cps->which);
8426     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8427         // [HGM] variantswitch: make engine aware of new variant
8428         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8429                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8430         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8431         SendToProgram(buf, cps);
8432         currentlyInitializedVariant = gameInfo.variant;
8433     }
8434     SendToProgram("force\n", cps);
8435     if (startedFromSetupPosition) {
8436         SendBoard(cps, backwardMostMove);
8437     if (appData.debugMode) {
8438         fprintf(debugFP, "feedMoves\n");
8439     }
8440     }
8441     for (i = backwardMostMove; i < upto; i++) {
8442         SendMoveToProgram(i, cps);
8443     }
8444 }
8445
8446
8447 void
8448 ResurrectChessProgram()
8449 {
8450      /* The chess program may have exited.
8451         If so, restart it and feed it all the moves made so far. */
8452
8453     if (appData.noChessProgram || first.pr != NoProc) return;
8454     
8455     StartChessProgram(&first);
8456     InitChessProgram(&first, FALSE);
8457     FeedMovesToProgram(&first, currentMove);
8458
8459     if (!first.sendTime) {
8460         /* can't tell gnuchess what its clock should read,
8461            so we bow to its notion. */
8462         ResetClocks();
8463         timeRemaining[0][currentMove] = whiteTimeRemaining;
8464         timeRemaining[1][currentMove] = blackTimeRemaining;
8465     }
8466
8467     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8468                 appData.icsEngineAnalyze) && first.analysisSupport) {
8469       SendToProgram("analyze\n", &first);
8470       first.analyzing = TRUE;
8471     }
8472 }
8473
8474 /*
8475  * Button procedures
8476  */
8477 void
8478 Reset(redraw, init)
8479      int redraw, init;
8480 {
8481     int i;
8482
8483     if (appData.debugMode) {
8484         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8485                 redraw, init, gameMode);
8486     }
8487     pausing = pauseExamInvalid = FALSE;
8488     startedFromSetupPosition = blackPlaysFirst = FALSE;
8489     firstMove = TRUE;
8490     whiteFlag = blackFlag = FALSE;
8491     userOfferedDraw = FALSE;
8492     hintRequested = bookRequested = FALSE;
8493     first.maybeThinking = FALSE;
8494     second.maybeThinking = FALSE;
8495     first.bookSuspend = FALSE; // [HGM] book
8496     second.bookSuspend = FALSE;
8497     thinkOutput[0] = NULLCHAR;
8498     lastHint[0] = NULLCHAR;
8499     ClearGameInfo(&gameInfo);
8500     gameInfo.variant = StringToVariant(appData.variant);
8501     ics_user_moved = ics_clock_paused = FALSE;
8502     ics_getting_history = H_FALSE;
8503     ics_gamenum = -1;
8504     white_holding[0] = black_holding[0] = NULLCHAR;
8505     ClearProgramStats();
8506     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8507     
8508     ResetFrontEnd();
8509     ClearHighlights();
8510     flipView = appData.flipView;
8511     ClearPremoveHighlights();
8512     gotPremove = FALSE;
8513     alarmSounded = FALSE;
8514
8515     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8516     if(appData.serverMovesName != NULL) {
8517         /* [HGM] prepare to make moves file for broadcasting */
8518         clock_t t = clock();
8519         if(serverMoves != NULL) fclose(serverMoves);
8520         serverMoves = fopen(appData.serverMovesName, "r");
8521         if(serverMoves != NULL) {
8522             fclose(serverMoves);
8523             /* delay 15 sec before overwriting, so all clients can see end */
8524             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8525         }
8526         serverMoves = fopen(appData.serverMovesName, "w");
8527     }
8528
8529     ExitAnalyzeMode();
8530     gameMode = BeginningOfGame;
8531     ModeHighlight();
8532     if(appData.icsActive) gameInfo.variant = VariantNormal;
8533     currentMove = forwardMostMove = backwardMostMove = 0;
8534     InitPosition(redraw);
8535     for (i = 0; i < MAX_MOVES; i++) {
8536         if (commentList[i] != NULL) {
8537             free(commentList[i]);
8538             commentList[i] = NULL;
8539         }
8540     }
8541     ResetClocks();
8542     timeRemaining[0][0] = whiteTimeRemaining;
8543     timeRemaining[1][0] = blackTimeRemaining;
8544     if (first.pr == NULL) {
8545         StartChessProgram(&first);
8546     }
8547     if (init) {
8548             InitChessProgram(&first, startedFromSetupPosition);
8549     }
8550     DisplayTitle("");
8551     DisplayMessage("", "");
8552     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8553     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8554 }
8555
8556 void
8557 AutoPlayGameLoop()
8558 {
8559     for (;;) {
8560         if (!AutoPlayOneMove())
8561           return;
8562         if (matchMode || appData.timeDelay == 0)
8563           continue;
8564         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8565           return;
8566         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8567         break;
8568     }
8569 }
8570
8571
8572 int
8573 AutoPlayOneMove()
8574 {
8575     int fromX, fromY, toX, toY;
8576
8577     if (appData.debugMode) {
8578       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8579     }
8580
8581     if (gameMode != PlayFromGameFile)
8582       return FALSE;
8583
8584     if (currentMove >= forwardMostMove) {
8585       gameMode = EditGame;
8586       ModeHighlight();
8587
8588       /* [AS] Clear current move marker at the end of a game */
8589       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8590
8591       return FALSE;
8592     }
8593     
8594     toX = moveList[currentMove][2] - AAA;
8595     toY = moveList[currentMove][3] - ONE;
8596
8597     if (moveList[currentMove][1] == '@') {
8598         if (appData.highlightLastMove) {
8599             SetHighlights(-1, -1, toX, toY);
8600         }
8601     } else {
8602         fromX = moveList[currentMove][0] - AAA;
8603         fromY = moveList[currentMove][1] - ONE;
8604
8605         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8606
8607         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8608
8609         if (appData.highlightLastMove) {
8610             SetHighlights(fromX, fromY, toX, toY);
8611         }
8612     }
8613     DisplayMove(currentMove);
8614     SendMoveToProgram(currentMove++, &first);
8615     DisplayBothClocks();
8616     DrawPosition(FALSE, boards[currentMove]);
8617     // [HGM] PV info: always display, routine tests if empty
8618     DisplayComment(currentMove - 1, commentList[currentMove]);
8619     return TRUE;
8620 }
8621
8622
8623 int
8624 LoadGameOneMove(readAhead)
8625      ChessMove readAhead;
8626 {
8627     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8628     char promoChar = NULLCHAR;
8629     ChessMove moveType;
8630     char move[MSG_SIZ];
8631     char *p, *q;
8632     
8633     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8634         gameMode != AnalyzeMode && gameMode != Training) {
8635         gameFileFP = NULL;
8636         return FALSE;
8637     }
8638     
8639     yyboardindex = forwardMostMove;
8640     if (readAhead != (ChessMove)0) {
8641       moveType = readAhead;
8642     } else {
8643       if (gameFileFP == NULL)
8644           return FALSE;
8645       moveType = (ChessMove) yylex();
8646     }
8647     
8648     done = FALSE;
8649     switch (moveType) {
8650       case Comment:
8651         if (appData.debugMode) 
8652           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8653         p = yy_text;
8654         if (*p == '{' || *p == '[' || *p == '(') {
8655             p[strlen(p) - 1] = NULLCHAR;
8656             p++;
8657         }
8658
8659         /* append the comment but don't display it */
8660         while (*p == '\n') p++;
8661         AppendComment(currentMove, p);
8662         return TRUE;
8663
8664       case WhiteCapturesEnPassant:
8665       case BlackCapturesEnPassant:
8666       case WhitePromotionChancellor:
8667       case BlackPromotionChancellor:
8668       case WhitePromotionArchbishop:
8669       case BlackPromotionArchbishop:
8670       case WhitePromotionCentaur:
8671       case BlackPromotionCentaur:
8672       case WhitePromotionQueen:
8673       case BlackPromotionQueen:
8674       case WhitePromotionRook:
8675       case BlackPromotionRook:
8676       case WhitePromotionBishop:
8677       case BlackPromotionBishop:
8678       case WhitePromotionKnight:
8679       case BlackPromotionKnight:
8680       case WhitePromotionKing:
8681       case BlackPromotionKing:
8682       case NormalMove:
8683       case WhiteKingSideCastle:
8684       case WhiteQueenSideCastle:
8685       case BlackKingSideCastle:
8686       case BlackQueenSideCastle:
8687       case WhiteKingSideCastleWild:
8688       case WhiteQueenSideCastleWild:
8689       case BlackKingSideCastleWild:
8690       case BlackQueenSideCastleWild:
8691       /* PUSH Fabien */
8692       case WhiteHSideCastleFR:
8693       case WhiteASideCastleFR:
8694       case BlackHSideCastleFR:
8695       case BlackASideCastleFR:
8696       /* POP Fabien */
8697         if (appData.debugMode)
8698           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8699         fromX = currentMoveString[0] - AAA;
8700         fromY = currentMoveString[1] - ONE;
8701         toX = currentMoveString[2] - AAA;
8702         toY = currentMoveString[3] - ONE;
8703         promoChar = currentMoveString[4];
8704         break;
8705
8706       case WhiteDrop:
8707       case BlackDrop:
8708         if (appData.debugMode)
8709           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8710         fromX = moveType == WhiteDrop ?
8711           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8712         (int) CharToPiece(ToLower(currentMoveString[0]));
8713         fromY = DROP_RANK;
8714         toX = currentMoveString[2] - AAA;
8715         toY = currentMoveString[3] - ONE;
8716         break;
8717
8718       case WhiteWins:
8719       case BlackWins:
8720       case GameIsDrawn:
8721       case GameUnfinished:
8722         if (appData.debugMode)
8723           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8724         p = strchr(yy_text, '{');
8725         if (p == NULL) p = strchr(yy_text, '(');
8726         if (p == NULL) {
8727             p = yy_text;
8728             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8729         } else {
8730             q = strchr(p, *p == '{' ? '}' : ')');
8731             if (q != NULL) *q = NULLCHAR;
8732             p++;
8733         }
8734         GameEnds(moveType, p, GE_FILE);
8735         done = TRUE;
8736         if (cmailMsgLoaded) {
8737             ClearHighlights();
8738             flipView = WhiteOnMove(currentMove);
8739             if (moveType == GameUnfinished) flipView = !flipView;
8740             if (appData.debugMode)
8741               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8742         }
8743         break;
8744
8745       case (ChessMove) 0:       /* end of file */
8746         if (appData.debugMode)
8747           fprintf(debugFP, "Parser hit end of file\n");
8748         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8749                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8750           case MT_NONE:
8751           case MT_CHECK:
8752             break;
8753           case MT_CHECKMATE:
8754           case MT_STAINMATE:
8755             if (WhiteOnMove(currentMove)) {
8756                 GameEnds(BlackWins, "Black mates", GE_FILE);
8757             } else {
8758                 GameEnds(WhiteWins, "White mates", GE_FILE);
8759             }
8760             break;
8761           case MT_STALEMATE:
8762             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8763             break;
8764         }
8765         done = TRUE;
8766         break;
8767
8768       case MoveNumberOne:
8769         if (lastLoadGameStart == GNUChessGame) {
8770             /* GNUChessGames have numbers, but they aren't move numbers */
8771             if (appData.debugMode)
8772               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8773                       yy_text, (int) moveType);
8774             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8775         }
8776         /* else fall thru */
8777
8778       case XBoardGame:
8779       case GNUChessGame:
8780       case PGNTag:
8781         /* Reached start of next game in file */
8782         if (appData.debugMode)
8783           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8784         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8785                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8786           case MT_NONE:
8787           case MT_CHECK:
8788             break;
8789           case MT_CHECKMATE:
8790           case MT_STAINMATE:
8791             if (WhiteOnMove(currentMove)) {
8792                 GameEnds(BlackWins, "Black mates", GE_FILE);
8793             } else {
8794                 GameEnds(WhiteWins, "White mates", GE_FILE);
8795             }
8796             break;
8797           case MT_STALEMATE:
8798             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8799             break;
8800         }
8801         done = TRUE;
8802         break;
8803
8804       case PositionDiagram:     /* should not happen; ignore */
8805       case ElapsedTime:         /* ignore */
8806       case NAG:                 /* ignore */
8807         if (appData.debugMode)
8808           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8809                   yy_text, (int) moveType);
8810         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8811
8812       case IllegalMove:
8813         if (appData.testLegality) {
8814             if (appData.debugMode)
8815               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8816             sprintf(move, _("Illegal move: %d.%s%s"),
8817                     (forwardMostMove / 2) + 1,
8818                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8819             DisplayError(move, 0);
8820             done = TRUE;
8821         } else {
8822             if (appData.debugMode)
8823               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8824                       yy_text, currentMoveString);
8825             fromX = currentMoveString[0] - AAA;
8826             fromY = currentMoveString[1] - ONE;
8827             toX = currentMoveString[2] - AAA;
8828             toY = currentMoveString[3] - ONE;
8829             promoChar = currentMoveString[4];
8830         }
8831         break;
8832
8833       case AmbiguousMove:
8834         if (appData.debugMode)
8835           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8836         sprintf(move, _("Ambiguous move: %d.%s%s"),
8837                 (forwardMostMove / 2) + 1,
8838                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8839         DisplayError(move, 0);
8840         done = TRUE;
8841         break;
8842
8843       default:
8844       case ImpossibleMove:
8845         if (appData.debugMode)
8846           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8847         sprintf(move, _("Illegal move: %d.%s%s"),
8848                 (forwardMostMove / 2) + 1,
8849                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8850         DisplayError(move, 0);
8851         done = TRUE;
8852         break;
8853     }
8854
8855     if (done) {
8856         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8857             DrawPosition(FALSE, boards[currentMove]);
8858             DisplayBothClocks();
8859             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8860               DisplayComment(currentMove - 1, commentList[currentMove]);
8861         }
8862         (void) StopLoadGameTimer();
8863         gameFileFP = NULL;
8864         cmailOldMove = forwardMostMove;
8865         return FALSE;
8866     } else {
8867         /* currentMoveString is set as a side-effect of yylex */
8868         strcat(currentMoveString, "\n");
8869         strcpy(moveList[forwardMostMove], currentMoveString);
8870         
8871         thinkOutput[0] = NULLCHAR;
8872         MakeMove(fromX, fromY, toX, toY, promoChar);
8873         currentMove = forwardMostMove;
8874         return TRUE;
8875     }
8876 }
8877
8878 /* Load the nth game from the given file */
8879 int
8880 LoadGameFromFile(filename, n, title, useList)
8881      char *filename;
8882      int n;
8883      char *title;
8884      /*Boolean*/ int useList;
8885 {
8886     FILE *f;
8887     char buf[MSG_SIZ];
8888
8889     if (strcmp(filename, "-") == 0) {
8890         f = stdin;
8891         title = "stdin";
8892     } else {
8893         f = fopen(filename, "rb");
8894         if (f == NULL) {
8895           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8896             DisplayError(buf, errno);
8897             return FALSE;
8898         }
8899     }
8900     if (fseek(f, 0, 0) == -1) {
8901         /* f is not seekable; probably a pipe */
8902         useList = FALSE;
8903     }
8904     if (useList && n == 0) {
8905         int error = GameListBuild(f);
8906         if (error) {
8907             DisplayError(_("Cannot build game list"), error);
8908         } else if (!ListEmpty(&gameList) &&
8909                    ((ListGame *) gameList.tailPred)->number > 1) {
8910             GameListPopUp(f, title);
8911             return TRUE;
8912         }
8913         GameListDestroy();
8914         n = 1;
8915     }
8916     if (n == 0) n = 1;
8917     return LoadGame(f, n, title, FALSE);
8918 }
8919
8920
8921 void
8922 MakeRegisteredMove()
8923 {
8924     int fromX, fromY, toX, toY;
8925     char promoChar;
8926     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8927         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8928           case CMAIL_MOVE:
8929           case CMAIL_DRAW:
8930             if (appData.debugMode)
8931               fprintf(debugFP, "Restoring %s for game %d\n",
8932                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8933     
8934             thinkOutput[0] = NULLCHAR;
8935             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8936             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8937             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8938             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8939             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8940             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8941             MakeMove(fromX, fromY, toX, toY, promoChar);
8942             ShowMove(fromX, fromY, toX, toY);
8943               
8944             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8945                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8946               case MT_NONE:
8947               case MT_CHECK:
8948                 break;
8949                 
8950               case MT_CHECKMATE:
8951               case MT_STAINMATE:
8952                 if (WhiteOnMove(currentMove)) {
8953                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8954                 } else {
8955                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8956                 }
8957                 break;
8958                 
8959               case MT_STALEMATE:
8960                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8961                 break;
8962             }
8963
8964             break;
8965             
8966           case CMAIL_RESIGN:
8967             if (WhiteOnMove(currentMove)) {
8968                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8969             } else {
8970                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8971             }
8972             break;
8973             
8974           case CMAIL_ACCEPT:
8975             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8976             break;
8977               
8978           default:
8979             break;
8980         }
8981     }
8982
8983     return;
8984 }
8985
8986 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8987 int
8988 CmailLoadGame(f, gameNumber, title, useList)
8989      FILE *f;
8990      int gameNumber;
8991      char *title;
8992      int useList;
8993 {
8994     int retVal;
8995
8996     if (gameNumber > nCmailGames) {
8997         DisplayError(_("No more games in this message"), 0);
8998         return FALSE;
8999     }
9000     if (f == lastLoadGameFP) {
9001         int offset = gameNumber - lastLoadGameNumber;
9002         if (offset == 0) {
9003             cmailMsg[0] = NULLCHAR;
9004             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9005                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9006                 nCmailMovesRegistered--;
9007             }
9008             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9009             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9010                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9011             }
9012         } else {
9013             if (! RegisterMove()) return FALSE;
9014         }
9015     }
9016
9017     retVal = LoadGame(f, gameNumber, title, useList);
9018
9019     /* Make move registered during previous look at this game, if any */
9020     MakeRegisteredMove();
9021
9022     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9023         commentList[currentMove]
9024           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9025         DisplayComment(currentMove - 1, commentList[currentMove]);
9026     }
9027
9028     return retVal;
9029 }
9030
9031 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9032 int
9033 ReloadGame(offset)
9034      int offset;
9035 {
9036     int gameNumber = lastLoadGameNumber + offset;
9037     if (lastLoadGameFP == NULL) {
9038         DisplayError(_("No game has been loaded yet"), 0);
9039         return FALSE;
9040     }
9041     if (gameNumber <= 0) {
9042         DisplayError(_("Can't back up any further"), 0);
9043         return FALSE;
9044     }
9045     if (cmailMsgLoaded) {
9046         return CmailLoadGame(lastLoadGameFP, gameNumber,
9047                              lastLoadGameTitle, lastLoadGameUseList);
9048     } else {
9049         return LoadGame(lastLoadGameFP, gameNumber,
9050                         lastLoadGameTitle, lastLoadGameUseList);
9051     }
9052 }
9053
9054
9055
9056 /* Load the nth game from open file f */
9057 int
9058 LoadGame(f, gameNumber, title, useList)
9059      FILE *f;
9060      int gameNumber;
9061      char *title;
9062      int useList;
9063 {
9064     ChessMove cm;
9065     char buf[MSG_SIZ];
9066     int gn = gameNumber;
9067     ListGame *lg = NULL;
9068     int numPGNTags = 0;
9069     int err;
9070     GameMode oldGameMode;
9071     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9072
9073     if (appData.debugMode) 
9074         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9075
9076     if (gameMode == Training )
9077         SetTrainingModeOff();
9078
9079     oldGameMode = gameMode;
9080     if (gameMode != BeginningOfGame) {
9081       Reset(FALSE, TRUE);
9082     }
9083
9084     gameFileFP = f;
9085     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9086         fclose(lastLoadGameFP);
9087     }
9088
9089     if (useList) {
9090         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9091         
9092         if (lg) {
9093             fseek(f, lg->offset, 0);
9094             GameListHighlight(gameNumber);
9095             gn = 1;
9096         }
9097         else {
9098             DisplayError(_("Game number out of range"), 0);
9099             return FALSE;
9100         }
9101     } else {
9102         GameListDestroy();
9103         if (fseek(f, 0, 0) == -1) {
9104             if (f == lastLoadGameFP ?
9105                 gameNumber == lastLoadGameNumber + 1 :
9106                 gameNumber == 1) {
9107                 gn = 1;
9108             } else {
9109                 DisplayError(_("Can't seek on game file"), 0);
9110                 return FALSE;
9111             }
9112         }
9113     }
9114     lastLoadGameFP = f;
9115     lastLoadGameNumber = gameNumber;
9116     strcpy(lastLoadGameTitle, title);
9117     lastLoadGameUseList = useList;
9118
9119     yynewfile(f);
9120
9121     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9122       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9123                 lg->gameInfo.black);
9124             DisplayTitle(buf);
9125     } else if (*title != NULLCHAR) {
9126         if (gameNumber > 1) {
9127             sprintf(buf, "%s %d", title, gameNumber);
9128             DisplayTitle(buf);
9129         } else {
9130             DisplayTitle(title);
9131         }
9132     }
9133
9134     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9135         gameMode = PlayFromGameFile;
9136         ModeHighlight();
9137     }
9138
9139     currentMove = forwardMostMove = backwardMostMove = 0;
9140     CopyBoard(boards[0], initialPosition);
9141     StopClocks();
9142
9143     /*
9144      * Skip the first gn-1 games in the file.
9145      * Also skip over anything that precedes an identifiable 
9146      * start of game marker, to avoid being confused by 
9147      * garbage at the start of the file.  Currently 
9148      * recognized start of game markers are the move number "1",
9149      * the pattern "gnuchess .* game", the pattern
9150      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9151      * A game that starts with one of the latter two patterns
9152      * will also have a move number 1, possibly
9153      * following a position diagram.
9154      * 5-4-02: Let's try being more lenient and allowing a game to
9155      * start with an unnumbered move.  Does that break anything?
9156      */
9157     cm = lastLoadGameStart = (ChessMove) 0;
9158     while (gn > 0) {
9159         yyboardindex = forwardMostMove;
9160         cm = (ChessMove) yylex();
9161         switch (cm) {
9162           case (ChessMove) 0:
9163             if (cmailMsgLoaded) {
9164                 nCmailGames = CMAIL_MAX_GAMES - gn;
9165             } else {
9166                 Reset(TRUE, TRUE);
9167                 DisplayError(_("Game not found in file"), 0);
9168             }
9169             return FALSE;
9170
9171           case GNUChessGame:
9172           case XBoardGame:
9173             gn--;
9174             lastLoadGameStart = cm;
9175             break;
9176             
9177           case MoveNumberOne:
9178             switch (lastLoadGameStart) {
9179               case GNUChessGame:
9180               case XBoardGame:
9181               case PGNTag:
9182                 break;
9183               case MoveNumberOne:
9184               case (ChessMove) 0:
9185                 gn--;           /* count this game */
9186                 lastLoadGameStart = cm;
9187                 break;
9188               default:
9189                 /* impossible */
9190                 break;
9191             }
9192             break;
9193
9194           case PGNTag:
9195             switch (lastLoadGameStart) {
9196               case GNUChessGame:
9197               case PGNTag:
9198               case MoveNumberOne:
9199               case (ChessMove) 0:
9200                 gn--;           /* count this game */
9201                 lastLoadGameStart = cm;
9202                 break;
9203               case XBoardGame:
9204                 lastLoadGameStart = cm; /* game counted already */
9205                 break;
9206               default:
9207                 /* impossible */
9208                 break;
9209             }
9210             if (gn > 0) {
9211                 do {
9212                     yyboardindex = forwardMostMove;
9213                     cm = (ChessMove) yylex();
9214                 } while (cm == PGNTag || cm == Comment);
9215             }
9216             break;
9217
9218           case WhiteWins:
9219           case BlackWins:
9220           case GameIsDrawn:
9221             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9222                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9223                     != CMAIL_OLD_RESULT) {
9224                     nCmailResults ++ ;
9225                     cmailResult[  CMAIL_MAX_GAMES
9226                                 - gn - 1] = CMAIL_OLD_RESULT;
9227                 }
9228             }
9229             break;
9230
9231           case NormalMove:
9232             /* Only a NormalMove can be at the start of a game
9233              * without a position diagram. */
9234             if (lastLoadGameStart == (ChessMove) 0) {
9235               gn--;
9236               lastLoadGameStart = MoveNumberOne;
9237             }
9238             break;
9239
9240           default:
9241             break;
9242         }
9243     }
9244     
9245     if (appData.debugMode)
9246       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9247
9248     if (cm == XBoardGame) {
9249         /* Skip any header junk before position diagram and/or move 1 */
9250         for (;;) {
9251             yyboardindex = forwardMostMove;
9252             cm = (ChessMove) yylex();
9253
9254             if (cm == (ChessMove) 0 ||
9255                 cm == GNUChessGame || cm == XBoardGame) {
9256                 /* Empty game; pretend end-of-file and handle later */
9257                 cm = (ChessMove) 0;
9258                 break;
9259             }
9260
9261             if (cm == MoveNumberOne || cm == PositionDiagram ||
9262                 cm == PGNTag || cm == Comment)
9263               break;
9264         }
9265     } else if (cm == GNUChessGame) {
9266         if (gameInfo.event != NULL) {
9267             free(gameInfo.event);
9268         }
9269         gameInfo.event = StrSave(yy_text);
9270     }   
9271
9272     startedFromSetupPosition = FALSE;
9273     while (cm == PGNTag) {
9274         if (appData.debugMode) 
9275           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9276         err = ParsePGNTag(yy_text, &gameInfo);
9277         if (!err) numPGNTags++;
9278
9279         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9280         if(gameInfo.variant != oldVariant) {
9281             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9282             InitPosition(TRUE);
9283             oldVariant = gameInfo.variant;
9284             if (appData.debugMode) 
9285               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9286         }
9287
9288
9289         if (gameInfo.fen != NULL) {
9290           Board initial_position;
9291           startedFromSetupPosition = TRUE;
9292           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9293             Reset(TRUE, TRUE);
9294             DisplayError(_("Bad FEN position in file"), 0);
9295             return FALSE;
9296           }
9297           CopyBoard(boards[0], initial_position);
9298           if (blackPlaysFirst) {
9299             currentMove = forwardMostMove = backwardMostMove = 1;
9300             CopyBoard(boards[1], initial_position);
9301             strcpy(moveList[0], "");
9302             strcpy(parseList[0], "");
9303             timeRemaining[0][1] = whiteTimeRemaining;
9304             timeRemaining[1][1] = blackTimeRemaining;
9305             if (commentList[0] != NULL) {
9306               commentList[1] = commentList[0];
9307               commentList[0] = NULL;
9308             }
9309           } else {
9310             currentMove = forwardMostMove = backwardMostMove = 0;
9311           }
9312           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9313           {   int i;
9314               initialRulePlies = FENrulePlies;
9315               epStatus[forwardMostMove] = FENepStatus;
9316               for( i=0; i< nrCastlingRights; i++ )
9317                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9318           }
9319           yyboardindex = forwardMostMove;
9320           free(gameInfo.fen);
9321           gameInfo.fen = NULL;
9322         }
9323
9324         yyboardindex = forwardMostMove;
9325         cm = (ChessMove) yylex();
9326
9327         /* Handle comments interspersed among the tags */
9328         while (cm == Comment) {
9329             char *p;
9330             if (appData.debugMode) 
9331               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9332             p = yy_text;
9333             if (*p == '{' || *p == '[' || *p == '(') {
9334                 p[strlen(p) - 1] = NULLCHAR;
9335                 p++;
9336             }
9337             while (*p == '\n') p++;
9338             AppendComment(currentMove, p);
9339             yyboardindex = forwardMostMove;
9340             cm = (ChessMove) yylex();
9341         }
9342     }
9343
9344     /* don't rely on existence of Event tag since if game was
9345      * pasted from clipboard the Event tag may not exist
9346      */
9347     if (numPGNTags > 0){
9348         char *tags;
9349         if (gameInfo.variant == VariantNormal) {
9350           gameInfo.variant = StringToVariant(gameInfo.event);
9351         }
9352         if (!matchMode) {
9353           if( appData.autoDisplayTags ) {
9354             tags = PGNTags(&gameInfo);
9355             TagsPopUp(tags, CmailMsg());
9356             free(tags);
9357           }
9358         }
9359     } else {
9360         /* Make something up, but don't display it now */
9361         SetGameInfo();
9362         TagsPopDown();
9363     }
9364
9365     if (cm == PositionDiagram) {
9366         int i, j;
9367         char *p;
9368         Board initial_position;
9369
9370         if (appData.debugMode)
9371           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9372
9373         if (!startedFromSetupPosition) {
9374             p = yy_text;
9375             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9376               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9377                 switch (*p) {
9378                   case '[':
9379                   case '-':
9380                   case ' ':
9381                   case '\t':
9382                   case '\n':
9383                   case '\r':
9384                     break;
9385                   default:
9386                     initial_position[i][j++] = CharToPiece(*p);
9387                     break;
9388                 }
9389             while (*p == ' ' || *p == '\t' ||
9390                    *p == '\n' || *p == '\r') p++;
9391         
9392             if (strncmp(p, "black", strlen("black"))==0)
9393               blackPlaysFirst = TRUE;
9394             else
9395               blackPlaysFirst = FALSE;
9396             startedFromSetupPosition = TRUE;
9397         
9398             CopyBoard(boards[0], initial_position);
9399             if (blackPlaysFirst) {
9400                 currentMove = forwardMostMove = backwardMostMove = 1;
9401                 CopyBoard(boards[1], initial_position);
9402                 strcpy(moveList[0], "");
9403                 strcpy(parseList[0], "");
9404                 timeRemaining[0][1] = whiteTimeRemaining;
9405                 timeRemaining[1][1] = blackTimeRemaining;
9406                 if (commentList[0] != NULL) {
9407                     commentList[1] = commentList[0];
9408                     commentList[0] = NULL;
9409                 }
9410             } else {
9411                 currentMove = forwardMostMove = backwardMostMove = 0;
9412             }
9413         }
9414         yyboardindex = forwardMostMove;
9415         cm = (ChessMove) yylex();
9416     }
9417
9418     if (first.pr == NoProc) {
9419         StartChessProgram(&first);
9420     }
9421     InitChessProgram(&first, FALSE);
9422     SendToProgram("force\n", &first);
9423     if (startedFromSetupPosition) {
9424         SendBoard(&first, forwardMostMove);
9425     if (appData.debugMode) {
9426         fprintf(debugFP, "Load Game\n");
9427     }
9428         DisplayBothClocks();
9429     }      
9430
9431     /* [HGM] server: flag to write setup moves in broadcast file as one */
9432     loadFlag = appData.suppressLoadMoves;
9433
9434     while (cm == Comment) {
9435         char *p;
9436         if (appData.debugMode) 
9437           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9438         p = yy_text;
9439         if (*p == '{' || *p == '[' || *p == '(') {
9440             p[strlen(p) - 1] = NULLCHAR;
9441             p++;
9442         }
9443         while (*p == '\n') p++;
9444         AppendComment(currentMove, p);
9445         yyboardindex = forwardMostMove;
9446         cm = (ChessMove) yylex();
9447     }
9448
9449     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9450         cm == WhiteWins || cm == BlackWins ||
9451         cm == GameIsDrawn || cm == GameUnfinished) {
9452         DisplayMessage("", _("No moves in game"));
9453         if (cmailMsgLoaded) {
9454             if (appData.debugMode)
9455               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9456             ClearHighlights();
9457             flipView = FALSE;
9458         }
9459         DrawPosition(FALSE, boards[currentMove]);
9460         DisplayBothClocks();
9461         gameMode = EditGame;
9462         ModeHighlight();
9463         gameFileFP = NULL;
9464         cmailOldMove = 0;
9465         return TRUE;
9466     }
9467
9468     // [HGM] PV info: routine tests if comment empty
9469     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9470         DisplayComment(currentMove - 1, commentList[currentMove]);
9471     }
9472     if (!matchMode && appData.timeDelay != 0) 
9473       DrawPosition(FALSE, boards[currentMove]);
9474
9475     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9476       programStats.ok_to_send = 1;
9477     }
9478
9479     /* if the first token after the PGN tags is a move
9480      * and not move number 1, retrieve it from the parser 
9481      */
9482     if (cm != MoveNumberOne)
9483         LoadGameOneMove(cm);
9484
9485     /* load the remaining moves from the file */
9486     while (LoadGameOneMove((ChessMove)0)) {
9487       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9488       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9489     }
9490
9491     /* rewind to the start of the game */
9492     currentMove = backwardMostMove;
9493
9494     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9495
9496     if (oldGameMode == AnalyzeFile ||
9497         oldGameMode == AnalyzeMode) {
9498       AnalyzeFileEvent();
9499     }
9500
9501     if (matchMode || appData.timeDelay == 0) {
9502       ToEndEvent();
9503       gameMode = EditGame;
9504       ModeHighlight();
9505     } else if (appData.timeDelay > 0) {
9506       AutoPlayGameLoop();
9507     }
9508
9509     if (appData.debugMode) 
9510         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9511
9512     loadFlag = 0; /* [HGM] true game starts */
9513     return TRUE;
9514 }
9515
9516 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9517 int
9518 ReloadPosition(offset)
9519      int offset;
9520 {
9521     int positionNumber = lastLoadPositionNumber + offset;
9522     if (lastLoadPositionFP == NULL) {
9523         DisplayError(_("No position has been loaded yet"), 0);
9524         return FALSE;
9525     }
9526     if (positionNumber <= 0) {
9527         DisplayError(_("Can't back up any further"), 0);
9528         return FALSE;
9529     }
9530     return LoadPosition(lastLoadPositionFP, positionNumber,
9531                         lastLoadPositionTitle);
9532 }
9533
9534 /* Load the nth position from the given file */
9535 int
9536 LoadPositionFromFile(filename, n, title)
9537      char *filename;
9538      int n;
9539      char *title;
9540 {
9541     FILE *f;
9542     char buf[MSG_SIZ];
9543
9544     if (strcmp(filename, "-") == 0) {
9545         return LoadPosition(stdin, n, "stdin");
9546     } else {
9547         f = fopen(filename, "rb");
9548         if (f == NULL) {
9549             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9550             DisplayError(buf, errno);
9551             return FALSE;
9552         } else {
9553             return LoadPosition(f, n, title);
9554         }
9555     }
9556 }
9557
9558 /* Load the nth position from the given open file, and close it */
9559 int
9560 LoadPosition(f, positionNumber, title)
9561      FILE *f;
9562      int positionNumber;
9563      char *title;
9564 {
9565     char *p, line[MSG_SIZ];
9566     Board initial_position;
9567     int i, j, fenMode, pn;
9568     
9569     if (gameMode == Training )
9570         SetTrainingModeOff();
9571
9572     if (gameMode != BeginningOfGame) {
9573         Reset(FALSE, TRUE);
9574     }
9575     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9576         fclose(lastLoadPositionFP);
9577     }
9578     if (positionNumber == 0) positionNumber = 1;
9579     lastLoadPositionFP = f;
9580     lastLoadPositionNumber = positionNumber;
9581     strcpy(lastLoadPositionTitle, title);
9582     if (first.pr == NoProc) {
9583       StartChessProgram(&first);
9584       InitChessProgram(&first, FALSE);
9585     }    
9586     pn = positionNumber;
9587     if (positionNumber < 0) {
9588         /* Negative position number means to seek to that byte offset */
9589         if (fseek(f, -positionNumber, 0) == -1) {
9590             DisplayError(_("Can't seek on position file"), 0);
9591             return FALSE;
9592         };
9593         pn = 1;
9594     } else {
9595         if (fseek(f, 0, 0) == -1) {
9596             if (f == lastLoadPositionFP ?
9597                 positionNumber == lastLoadPositionNumber + 1 :
9598                 positionNumber == 1) {
9599                 pn = 1;
9600             } else {
9601                 DisplayError(_("Can't seek on position file"), 0);
9602                 return FALSE;
9603             }
9604         }
9605     }
9606     /* See if this file is FEN or old-style xboard */
9607     if (fgets(line, MSG_SIZ, f) == NULL) {
9608         DisplayError(_("Position not found in file"), 0);
9609         return FALSE;
9610     }
9611     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9612     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9613
9614     if (pn >= 2) {
9615         if (fenMode || line[0] == '#') pn--;
9616         while (pn > 0) {
9617             /* skip positions before number pn */
9618             if (fgets(line, MSG_SIZ, f) == NULL) {
9619                 Reset(TRUE, TRUE);
9620                 DisplayError(_("Position not found in file"), 0);
9621                 return FALSE;
9622             }
9623             if (fenMode || line[0] == '#') pn--;
9624         }
9625     }
9626
9627     if (fenMode) {
9628         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9629             DisplayError(_("Bad FEN position in file"), 0);
9630             return FALSE;
9631         }
9632     } else {
9633         (void) fgets(line, MSG_SIZ, f);
9634         (void) fgets(line, MSG_SIZ, f);
9635     
9636         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9637             (void) fgets(line, MSG_SIZ, f);
9638             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9639                 if (*p == ' ')
9640                   continue;
9641                 initial_position[i][j++] = CharToPiece(*p);
9642             }
9643         }
9644     
9645         blackPlaysFirst = FALSE;
9646         if (!feof(f)) {
9647             (void) fgets(line, MSG_SIZ, f);
9648             if (strncmp(line, "black", strlen("black"))==0)
9649               blackPlaysFirst = TRUE;
9650         }
9651     }
9652     startedFromSetupPosition = TRUE;
9653     
9654     SendToProgram("force\n", &first);
9655     CopyBoard(boards[0], initial_position);
9656     if (blackPlaysFirst) {
9657         currentMove = forwardMostMove = backwardMostMove = 1;
9658         strcpy(moveList[0], "");
9659         strcpy(parseList[0], "");
9660         CopyBoard(boards[1], initial_position);
9661         DisplayMessage("", _("Black to play"));
9662     } else {
9663         currentMove = forwardMostMove = backwardMostMove = 0;
9664         DisplayMessage("", _("White to play"));
9665     }
9666           /* [HGM] copy FEN attributes as well */
9667           {   int i;
9668               initialRulePlies = FENrulePlies;
9669               epStatus[forwardMostMove] = FENepStatus;
9670               for( i=0; i< nrCastlingRights; i++ )
9671                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9672           }
9673     SendBoard(&first, forwardMostMove);
9674     if (appData.debugMode) {
9675 int i, j;
9676   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9677   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9678         fprintf(debugFP, "Load Position\n");
9679     }
9680
9681     if (positionNumber > 1) {
9682         sprintf(line, "%s %d", title, positionNumber);
9683         DisplayTitle(line);
9684     } else {
9685         DisplayTitle(title);
9686     }
9687     gameMode = EditGame;
9688     ModeHighlight();
9689     ResetClocks();
9690     timeRemaining[0][1] = whiteTimeRemaining;
9691     timeRemaining[1][1] = blackTimeRemaining;
9692     DrawPosition(FALSE, boards[currentMove]);
9693    
9694     return TRUE;
9695 }
9696
9697
9698 void
9699 CopyPlayerNameIntoFileName(dest, src)
9700      char **dest, *src;
9701 {
9702     while (*src != NULLCHAR && *src != ',') {
9703         if (*src == ' ') {
9704             *(*dest)++ = '_';
9705             src++;
9706         } else {
9707             *(*dest)++ = *src++;
9708         }
9709     }
9710 }
9711
9712 char *DefaultFileName(ext)
9713      char *ext;
9714 {
9715     static char def[MSG_SIZ];
9716     char *p;
9717
9718     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9719         p = def;
9720         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9721         *p++ = '-';
9722         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9723         *p++ = '.';
9724         strcpy(p, ext);
9725     } else {
9726         def[0] = NULLCHAR;
9727     }
9728     return def;
9729 }
9730
9731 /* Save the current game to the given file */
9732 int
9733 SaveGameToFile(filename, append)
9734      char *filename;
9735      int append;
9736 {
9737     FILE *f;
9738     char buf[MSG_SIZ];
9739
9740     if (strcmp(filename, "-") == 0) {
9741         return SaveGame(stdout, 0, NULL);
9742     } else {
9743         f = fopen(filename, append ? "a" : "w");
9744         if (f == NULL) {
9745             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9746             DisplayError(buf, errno);
9747             return FALSE;
9748         } else {
9749             return SaveGame(f, 0, NULL);
9750         }
9751     }
9752 }
9753
9754 char *
9755 SavePart(str)
9756      char *str;
9757 {
9758     static char buf[MSG_SIZ];
9759     char *p;
9760     
9761     p = strchr(str, ' ');
9762     if (p == NULL) return str;
9763     strncpy(buf, str, p - str);
9764     buf[p - str] = NULLCHAR;
9765     return buf;
9766 }
9767
9768 #define PGN_MAX_LINE 75
9769
9770 #define PGN_SIDE_WHITE  0
9771 #define PGN_SIDE_BLACK  1
9772
9773 /* [AS] */
9774 static int FindFirstMoveOutOfBook( int side )
9775 {
9776     int result = -1;
9777
9778     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9779         int index = backwardMostMove;
9780         int has_book_hit = 0;
9781
9782         if( (index % 2) != side ) {
9783             index++;
9784         }
9785
9786         while( index < forwardMostMove ) {
9787             /* Check to see if engine is in book */
9788             int depth = pvInfoList[index].depth;
9789             int score = pvInfoList[index].score;
9790             int in_book = 0;
9791
9792             if( depth <= 2 ) {
9793                 in_book = 1;
9794             }
9795             else if( score == 0 && depth == 63 ) {
9796                 in_book = 1; /* Zappa */
9797             }
9798             else if( score == 2 && depth == 99 ) {
9799                 in_book = 1; /* Abrok */
9800             }
9801
9802             has_book_hit += in_book;
9803
9804             if( ! in_book ) {
9805                 result = index;
9806
9807                 break;
9808             }
9809
9810             index += 2;
9811         }
9812     }
9813
9814     return result;
9815 }
9816
9817 /* [AS] */
9818 void GetOutOfBookInfo( char * buf )
9819 {
9820     int oob[2];
9821     int i;
9822     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9823
9824     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9825     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9826
9827     *buf = '\0';
9828
9829     if( oob[0] >= 0 || oob[1] >= 0 ) {
9830         for( i=0; i<2; i++ ) {
9831             int idx = oob[i];
9832
9833             if( idx >= 0 ) {
9834                 if( i > 0 && oob[0] >= 0 ) {
9835                     strcat( buf, "   " );
9836                 }
9837
9838                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9839                 sprintf( buf+strlen(buf), "%s%.2f", 
9840                     pvInfoList[idx].score >= 0 ? "+" : "",
9841                     pvInfoList[idx].score / 100.0 );
9842             }
9843         }
9844     }
9845 }
9846
9847 /* Save game in PGN style and close the file */
9848 int
9849 SaveGamePGN(f)
9850      FILE *f;
9851 {
9852     int i, offset, linelen, newblock;
9853     time_t tm;
9854 //    char *movetext;
9855     char numtext[32];
9856     int movelen, numlen, blank;
9857     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9858
9859     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9860     
9861     tm = time((time_t *) NULL);
9862     
9863     PrintPGNTags(f, &gameInfo);
9864     
9865     if (backwardMostMove > 0 || startedFromSetupPosition) {
9866         char *fen = PositionToFEN(backwardMostMove, NULL);
9867         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9868         fprintf(f, "\n{--------------\n");
9869         PrintPosition(f, backwardMostMove);
9870         fprintf(f, "--------------}\n");
9871         free(fen);
9872     }
9873     else {
9874         /* [AS] Out of book annotation */
9875         if( appData.saveOutOfBookInfo ) {
9876             char buf[64];
9877
9878             GetOutOfBookInfo( buf );
9879
9880             if( buf[0] != '\0' ) {
9881                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9882             }
9883         }
9884
9885         fprintf(f, "\n");
9886     }
9887
9888     i = backwardMostMove;
9889     linelen = 0;
9890     newblock = TRUE;
9891
9892     while (i < forwardMostMove) {
9893         /* Print comments preceding this move */
9894         if (commentList[i] != NULL) {
9895             if (linelen > 0) fprintf(f, "\n");
9896             fprintf(f, "{\n%s}\n", commentList[i]);
9897             linelen = 0;
9898             newblock = TRUE;
9899         }
9900
9901         /* Format move number */
9902         if ((i % 2) == 0) {
9903             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9904         } else {
9905             if (newblock) {
9906                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9907             } else {
9908                 numtext[0] = NULLCHAR;
9909             }
9910         }
9911         numlen = strlen(numtext);
9912         newblock = FALSE;
9913
9914         /* Print move number */
9915         blank = linelen > 0 && numlen > 0;
9916         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9917             fprintf(f, "\n");
9918             linelen = 0;
9919             blank = 0;
9920         }
9921         if (blank) {
9922             fprintf(f, " ");
9923             linelen++;
9924         }
9925         fprintf(f, "%s", numtext);
9926         linelen += numlen;
9927
9928         /* Get move */
9929         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9930         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9931
9932         /* Print move */
9933         blank = linelen > 0 && movelen > 0;
9934         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9935             fprintf(f, "\n");
9936             linelen = 0;
9937             blank = 0;
9938         }
9939         if (blank) {
9940             fprintf(f, " ");
9941             linelen++;
9942         }
9943         fprintf(f, "%s", move_buffer);
9944         linelen += movelen;
9945
9946         /* [AS] Add PV info if present */
9947         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9948             /* [HGM] add time */
9949             char buf[MSG_SIZ]; int seconds = 0;
9950
9951             if(i >= backwardMostMove) {
9952                 if(WhiteOnMove(i))
9953                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9954                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9955                 else
9956                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9957                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9958             }
9959             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9960
9961             if( seconds <= 0) buf[0] = 0; else
9962             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9963                 seconds = (seconds + 4)/10; // round to full seconds
9964                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9965                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9966             }
9967
9968             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9969                 pvInfoList[i].score >= 0 ? "+" : "",
9970                 pvInfoList[i].score / 100.0,
9971                 pvInfoList[i].depth,
9972                 buf );
9973
9974             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9975
9976             /* Print score/depth */
9977             blank = linelen > 0 && movelen > 0;
9978             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9979                 fprintf(f, "\n");
9980                 linelen = 0;
9981                 blank = 0;
9982             }
9983             if (blank) {
9984                 fprintf(f, " ");
9985                 linelen++;
9986             }
9987             fprintf(f, "%s", move_buffer);
9988             linelen += movelen;
9989         }
9990
9991         i++;
9992     }
9993     
9994     /* Start a new line */
9995     if (linelen > 0) fprintf(f, "\n");
9996
9997     /* Print comments after last move */
9998     if (commentList[i] != NULL) {
9999         fprintf(f, "{\n%s}\n", commentList[i]);
10000     }
10001
10002     /* Print result */
10003     if (gameInfo.resultDetails != NULL &&
10004         gameInfo.resultDetails[0] != NULLCHAR) {
10005         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10006                 PGNResult(gameInfo.result));
10007     } else {
10008         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10009     }
10010
10011     fclose(f);
10012     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10013     return TRUE;
10014 }
10015
10016 /* Save game in old style and close the file */
10017 int
10018 SaveGameOldStyle(f)
10019      FILE *f;
10020 {
10021     int i, offset;
10022     time_t tm;
10023     
10024     tm = time((time_t *) NULL);
10025     
10026     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10027     PrintOpponents(f);
10028     
10029     if (backwardMostMove > 0 || startedFromSetupPosition) {
10030         fprintf(f, "\n[--------------\n");
10031         PrintPosition(f, backwardMostMove);
10032         fprintf(f, "--------------]\n");
10033     } else {
10034         fprintf(f, "\n");
10035     }
10036
10037     i = backwardMostMove;
10038     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10039
10040     while (i < forwardMostMove) {
10041         if (commentList[i] != NULL) {
10042             fprintf(f, "[%s]\n", commentList[i]);
10043         }
10044
10045         if ((i % 2) == 1) {
10046             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10047             i++;
10048         } else {
10049             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10050             i++;
10051             if (commentList[i] != NULL) {
10052                 fprintf(f, "\n");
10053                 continue;
10054             }
10055             if (i >= forwardMostMove) {
10056                 fprintf(f, "\n");
10057                 break;
10058             }
10059             fprintf(f, "%s\n", parseList[i]);
10060             i++;
10061         }
10062     }
10063     
10064     if (commentList[i] != NULL) {
10065         fprintf(f, "[%s]\n", commentList[i]);
10066     }
10067
10068     /* This isn't really the old style, but it's close enough */
10069     if (gameInfo.resultDetails != NULL &&
10070         gameInfo.resultDetails[0] != NULLCHAR) {
10071         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10072                 gameInfo.resultDetails);
10073     } else {
10074         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10075     }
10076
10077     fclose(f);
10078     return TRUE;
10079 }
10080
10081 /* Save the current game to open file f and close the file */
10082 int
10083 SaveGame(f, dummy, dummy2)
10084      FILE *f;
10085      int dummy;
10086      char *dummy2;
10087 {
10088     if (gameMode == EditPosition) EditPositionDone();
10089     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10090     if (appData.oldSaveStyle)
10091       return SaveGameOldStyle(f);
10092     else
10093       return SaveGamePGN(f);
10094 }
10095
10096 /* Save the current position to the given file */
10097 int
10098 SavePositionToFile(filename)
10099      char *filename;
10100 {
10101     FILE *f;
10102     char buf[MSG_SIZ];
10103
10104     if (strcmp(filename, "-") == 0) {
10105         return SavePosition(stdout, 0, NULL);
10106     } else {
10107         f = fopen(filename, "a");
10108         if (f == NULL) {
10109             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10110             DisplayError(buf, errno);
10111             return FALSE;
10112         } else {
10113             SavePosition(f, 0, NULL);
10114             return TRUE;
10115         }
10116     }
10117 }
10118
10119 /* Save the current position to the given open file and close the file */
10120 int
10121 SavePosition(f, dummy, dummy2)
10122      FILE *f;
10123      int dummy;
10124      char *dummy2;
10125 {
10126     time_t tm;
10127     char *fen;
10128     
10129     if (appData.oldSaveStyle) {
10130         tm = time((time_t *) NULL);
10131     
10132         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10133         PrintOpponents(f);
10134         fprintf(f, "[--------------\n");
10135         PrintPosition(f, currentMove);
10136         fprintf(f, "--------------]\n");
10137     } else {
10138         fen = PositionToFEN(currentMove, NULL);
10139         fprintf(f, "%s\n", fen);
10140         free(fen);
10141     }
10142     fclose(f);
10143     return TRUE;
10144 }
10145
10146 void
10147 ReloadCmailMsgEvent(unregister)
10148      int unregister;
10149 {
10150 #if !WIN32
10151     static char *inFilename = NULL;
10152     static char *outFilename;
10153     int i;
10154     struct stat inbuf, outbuf;
10155     int status;
10156     
10157     /* Any registered moves are unregistered if unregister is set, */
10158     /* i.e. invoked by the signal handler */
10159     if (unregister) {
10160         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10161             cmailMoveRegistered[i] = FALSE;
10162             if (cmailCommentList[i] != NULL) {
10163                 free(cmailCommentList[i]);
10164                 cmailCommentList[i] = NULL;
10165             }
10166         }
10167         nCmailMovesRegistered = 0;
10168     }
10169
10170     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10171         cmailResult[i] = CMAIL_NOT_RESULT;
10172     }
10173     nCmailResults = 0;
10174
10175     if (inFilename == NULL) {
10176         /* Because the filenames are static they only get malloced once  */
10177         /* and they never get freed                                      */
10178         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10179         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10180
10181         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10182         sprintf(outFilename, "%s.out", appData.cmailGameName);
10183     }
10184     
10185     status = stat(outFilename, &outbuf);
10186     if (status < 0) {
10187         cmailMailedMove = FALSE;
10188     } else {
10189         status = stat(inFilename, &inbuf);
10190         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10191     }
10192     
10193     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10194        counts the games, notes how each one terminated, etc.
10195        
10196        It would be nice to remove this kludge and instead gather all
10197        the information while building the game list.  (And to keep it
10198        in the game list nodes instead of having a bunch of fixed-size
10199        parallel arrays.)  Note this will require getting each game's
10200        termination from the PGN tags, as the game list builder does
10201        not process the game moves.  --mann
10202        */
10203     cmailMsgLoaded = TRUE;
10204     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10205     
10206     /* Load first game in the file or popup game menu */
10207     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10208
10209 #endif /* !WIN32 */
10210     return;
10211 }
10212
10213 int
10214 RegisterMove()
10215 {
10216     FILE *f;
10217     char string[MSG_SIZ];
10218
10219     if (   cmailMailedMove
10220         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10221         return TRUE;            /* Allow free viewing  */
10222     }
10223
10224     /* Unregister move to ensure that we don't leave RegisterMove        */
10225     /* with the move registered when the conditions for registering no   */
10226     /* longer hold                                                       */
10227     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10228         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10229         nCmailMovesRegistered --;
10230
10231         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10232           {
10233               free(cmailCommentList[lastLoadGameNumber - 1]);
10234               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10235           }
10236     }
10237
10238     if (cmailOldMove == -1) {
10239         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10240         return FALSE;
10241     }
10242
10243     if (currentMove > cmailOldMove + 1) {
10244         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10245         return FALSE;
10246     }
10247
10248     if (currentMove < cmailOldMove) {
10249         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10250         return FALSE;
10251     }
10252
10253     if (forwardMostMove > currentMove) {
10254         /* Silently truncate extra moves */
10255         TruncateGame();
10256     }
10257
10258     if (   (currentMove == cmailOldMove + 1)
10259         || (   (currentMove == cmailOldMove)
10260             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10261                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10262         if (gameInfo.result != GameUnfinished) {
10263             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10264         }
10265
10266         if (commentList[currentMove] != NULL) {
10267             cmailCommentList[lastLoadGameNumber - 1]
10268               = StrSave(commentList[currentMove]);
10269         }
10270         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10271
10272         if (appData.debugMode)
10273           fprintf(debugFP, "Saving %s for game %d\n",
10274                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10275
10276         sprintf(string,
10277                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10278         
10279         f = fopen(string, "w");
10280         if (appData.oldSaveStyle) {
10281             SaveGameOldStyle(f); /* also closes the file */
10282             
10283             sprintf(string, "%s.pos.out", appData.cmailGameName);
10284             f = fopen(string, "w");
10285             SavePosition(f, 0, NULL); /* also closes the file */
10286         } else {
10287             fprintf(f, "{--------------\n");
10288             PrintPosition(f, currentMove);
10289             fprintf(f, "--------------}\n\n");
10290             
10291             SaveGame(f, 0, NULL); /* also closes the file*/
10292         }
10293         
10294         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10295         nCmailMovesRegistered ++;
10296     } else if (nCmailGames == 1) {
10297         DisplayError(_("You have not made a move yet"), 0);
10298         return FALSE;
10299     }
10300
10301     return TRUE;
10302 }
10303
10304 void
10305 MailMoveEvent()
10306 {
10307 #if !WIN32
10308     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10309     FILE *commandOutput;
10310     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10311     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10312     int nBuffers;
10313     int i;
10314     int archived;
10315     char *arcDir;
10316
10317     if (! cmailMsgLoaded) {
10318         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10319         return;
10320     }
10321
10322     if (nCmailGames == nCmailResults) {
10323         DisplayError(_("No unfinished games"), 0);
10324         return;
10325     }
10326
10327 #if CMAIL_PROHIBIT_REMAIL
10328     if (cmailMailedMove) {
10329         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);
10330         DisplayError(msg, 0);
10331         return;
10332     }
10333 #endif
10334
10335     if (! (cmailMailedMove || RegisterMove())) return;
10336     
10337     if (   cmailMailedMove
10338         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10339         sprintf(string, partCommandString,
10340                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10341         commandOutput = popen(string, "r");
10342
10343         if (commandOutput == NULL) {
10344             DisplayError(_("Failed to invoke cmail"), 0);
10345         } else {
10346             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10347                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10348             }
10349             if (nBuffers > 1) {
10350                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10351                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10352                 nBytes = MSG_SIZ - 1;
10353             } else {
10354                 (void) memcpy(msg, buffer, nBytes);
10355             }
10356             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10357
10358             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10359                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10360
10361                 archived = TRUE;
10362                 for (i = 0; i < nCmailGames; i ++) {
10363                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10364                         archived = FALSE;
10365                     }
10366                 }
10367                 if (   archived
10368                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10369                         != NULL)) {
10370                     sprintf(buffer, "%s/%s.%s.archive",
10371                             arcDir,
10372                             appData.cmailGameName,
10373                             gameInfo.date);
10374                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10375                     cmailMsgLoaded = FALSE;
10376                 }
10377             }
10378
10379             DisplayInformation(msg);
10380             pclose(commandOutput);
10381         }
10382     } else {
10383         if ((*cmailMsg) != '\0') {
10384             DisplayInformation(cmailMsg);
10385         }
10386     }
10387
10388     return;
10389 #endif /* !WIN32 */
10390 }
10391
10392 char *
10393 CmailMsg()
10394 {
10395 #if WIN32
10396     return NULL;
10397 #else
10398     int  prependComma = 0;
10399     char number[5];
10400     char string[MSG_SIZ];       /* Space for game-list */
10401     int  i;
10402     
10403     if (!cmailMsgLoaded) return "";
10404
10405     if (cmailMailedMove) {
10406         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10407     } else {
10408         /* Create a list of games left */
10409         sprintf(string, "[");
10410         for (i = 0; i < nCmailGames; i ++) {
10411             if (! (   cmailMoveRegistered[i]
10412                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10413                 if (prependComma) {
10414                     sprintf(number, ",%d", i + 1);
10415                 } else {
10416                     sprintf(number, "%d", i + 1);
10417                     prependComma = 1;
10418                 }
10419                 
10420                 strcat(string, number);
10421             }
10422         }
10423         strcat(string, "]");
10424
10425         if (nCmailMovesRegistered + nCmailResults == 0) {
10426             switch (nCmailGames) {
10427               case 1:
10428                 sprintf(cmailMsg,
10429                         _("Still need to make move for game\n"));
10430                 break;
10431                 
10432               case 2:
10433                 sprintf(cmailMsg,
10434                         _("Still need to make moves for both games\n"));
10435                 break;
10436                 
10437               default:
10438                 sprintf(cmailMsg,
10439                         _("Still need to make moves for all %d games\n"),
10440                         nCmailGames);
10441                 break;
10442             }
10443         } else {
10444             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10445               case 1:
10446                 sprintf(cmailMsg,
10447                         _("Still need to make a move for game %s\n"),
10448                         string);
10449                 break;
10450                 
10451               case 0:
10452                 if (nCmailResults == nCmailGames) {
10453                     sprintf(cmailMsg, _("No unfinished games\n"));
10454                 } else {
10455                     sprintf(cmailMsg, _("Ready to send mail\n"));
10456                 }
10457                 break;
10458                 
10459               default:
10460                 sprintf(cmailMsg,
10461                         _("Still need to make moves for games %s\n"),
10462                         string);
10463             }
10464         }
10465     }
10466     return cmailMsg;
10467 #endif /* WIN32 */
10468 }
10469
10470 void
10471 ResetGameEvent()
10472 {
10473     if (gameMode == Training)
10474       SetTrainingModeOff();
10475
10476     Reset(TRUE, TRUE);
10477     cmailMsgLoaded = FALSE;
10478     if (appData.icsActive) {
10479       SendToICS(ics_prefix);
10480       SendToICS("refresh\n");
10481     }
10482 }
10483
10484 void
10485 ExitEvent(status)
10486      int status;
10487 {
10488     exiting++;
10489     if (exiting > 2) {
10490       /* Give up on clean exit */
10491       exit(status);
10492     }
10493     if (exiting > 1) {
10494       /* Keep trying for clean exit */
10495       return;
10496     }
10497
10498     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10499
10500     if (telnetISR != NULL) {
10501       RemoveInputSource(telnetISR);
10502     }
10503     if (icsPR != NoProc) {
10504       DestroyChildProcess(icsPR, TRUE);
10505     }
10506
10507     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10508     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10509
10510     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10511     /* make sure this other one finishes before killing it!                  */
10512     if(endingGame) { int count = 0;
10513         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10514         while(endingGame && count++ < 10) DoSleep(1);
10515         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10516     }
10517
10518     /* Kill off chess programs */
10519     if (first.pr != NoProc) {
10520         ExitAnalyzeMode();
10521         
10522         DoSleep( appData.delayBeforeQuit );
10523         SendToProgram("quit\n", &first);
10524         DoSleep( appData.delayAfterQuit );
10525         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10526     }
10527     if (second.pr != NoProc) {
10528         DoSleep( appData.delayBeforeQuit );
10529         SendToProgram("quit\n", &second);
10530         DoSleep( appData.delayAfterQuit );
10531         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10532     }
10533     if (first.isr != NULL) {
10534         RemoveInputSource(first.isr);
10535     }
10536     if (second.isr != NULL) {
10537         RemoveInputSource(second.isr);
10538     }
10539
10540     ShutDownFrontEnd();
10541     exit(status);
10542 }
10543
10544 void
10545 PauseEvent()
10546 {
10547     if (appData.debugMode)
10548         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10549     if (pausing) {
10550         pausing = FALSE;
10551         ModeHighlight();
10552         if (gameMode == MachinePlaysWhite ||
10553             gameMode == MachinePlaysBlack) {
10554             StartClocks();
10555         } else {
10556             DisplayBothClocks();
10557         }
10558         if (gameMode == PlayFromGameFile) {
10559             if (appData.timeDelay >= 0) 
10560                 AutoPlayGameLoop();
10561         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10562             Reset(FALSE, TRUE);
10563             SendToICS(ics_prefix);
10564             SendToICS("refresh\n");
10565         } else if (currentMove < forwardMostMove) {
10566             ForwardInner(forwardMostMove);
10567         }
10568         pauseExamInvalid = FALSE;
10569     } else {
10570         switch (gameMode) {
10571           default:
10572             return;
10573           case IcsExamining:
10574             pauseExamForwardMostMove = forwardMostMove;
10575             pauseExamInvalid = FALSE;
10576             /* fall through */
10577           case IcsObserving:
10578           case IcsPlayingWhite:
10579           case IcsPlayingBlack:
10580             pausing = TRUE;
10581             ModeHighlight();
10582             return;
10583           case PlayFromGameFile:
10584             (void) StopLoadGameTimer();
10585             pausing = TRUE;
10586             ModeHighlight();
10587             break;
10588           case BeginningOfGame:
10589             if (appData.icsActive) return;
10590             /* else fall through */
10591           case MachinePlaysWhite:
10592           case MachinePlaysBlack:
10593           case TwoMachinesPlay:
10594             if (forwardMostMove == 0)
10595               return;           /* don't pause if no one has moved */
10596             if ((gameMode == MachinePlaysWhite &&
10597                  !WhiteOnMove(forwardMostMove)) ||
10598                 (gameMode == MachinePlaysBlack &&
10599                  WhiteOnMove(forwardMostMove))) {
10600                 StopClocks();
10601             }
10602             pausing = TRUE;
10603             ModeHighlight();
10604             break;
10605         }
10606     }
10607 }
10608
10609 void
10610 EditCommentEvent()
10611 {
10612     char title[MSG_SIZ];
10613
10614     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10615         strcpy(title, _("Edit comment"));
10616     } else {
10617         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10618                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10619                 parseList[currentMove - 1]);
10620     }
10621
10622     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10623 }
10624
10625
10626 void
10627 EditTagsEvent()
10628 {
10629     char *tags = PGNTags(&gameInfo);
10630     EditTagsPopUp(tags);
10631     free(tags);
10632 }
10633
10634 void
10635 AnalyzeModeEvent()
10636 {
10637     if (appData.noChessProgram || gameMode == AnalyzeMode)
10638       return;
10639
10640     if (gameMode != AnalyzeFile) {
10641         if (!appData.icsEngineAnalyze) {
10642                EditGameEvent();
10643                if (gameMode != EditGame) return;
10644         }
10645         ResurrectChessProgram();
10646         SendToProgram("analyze\n", &first);
10647         first.analyzing = TRUE;
10648         /*first.maybeThinking = TRUE;*/
10649         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10650         EngineOutputPopUp();
10651     }
10652     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10653     pausing = FALSE;
10654     ModeHighlight();
10655     SetGameInfo();
10656
10657     StartAnalysisClock();
10658     GetTimeMark(&lastNodeCountTime);
10659     lastNodeCount = 0;
10660 }
10661
10662 void
10663 AnalyzeFileEvent()
10664 {
10665     if (appData.noChessProgram || gameMode == AnalyzeFile)
10666       return;
10667
10668     if (gameMode != AnalyzeMode) {
10669         EditGameEvent();
10670         if (gameMode != EditGame) return;
10671         ResurrectChessProgram();
10672         SendToProgram("analyze\n", &first);
10673         first.analyzing = TRUE;
10674         /*first.maybeThinking = TRUE;*/
10675         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10676         EngineOutputPopUp();
10677     }
10678     gameMode = AnalyzeFile;
10679     pausing = FALSE;
10680     ModeHighlight();
10681     SetGameInfo();
10682
10683     StartAnalysisClock();
10684     GetTimeMark(&lastNodeCountTime);
10685     lastNodeCount = 0;
10686 }
10687
10688 void
10689 MachineWhiteEvent()
10690 {
10691     char buf[MSG_SIZ];
10692     char *bookHit = NULL;
10693
10694     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10695       return;
10696
10697
10698     if (gameMode == PlayFromGameFile || 
10699         gameMode == TwoMachinesPlay  || 
10700         gameMode == Training         || 
10701         gameMode == AnalyzeMode      || 
10702         gameMode == EndOfGame)
10703         EditGameEvent();
10704
10705     if (gameMode == EditPosition) 
10706         EditPositionDone();
10707
10708     if (!WhiteOnMove(currentMove)) {
10709         DisplayError(_("It is not White's turn"), 0);
10710         return;
10711     }
10712   
10713     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10714       ExitAnalyzeMode();
10715
10716     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10717         gameMode == AnalyzeFile)
10718         TruncateGame();
10719
10720     ResurrectChessProgram();    /* in case it isn't running */
10721     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10722         gameMode = MachinePlaysWhite;
10723         ResetClocks();
10724     } else
10725     gameMode = MachinePlaysWhite;
10726     pausing = FALSE;
10727     ModeHighlight();
10728     SetGameInfo();
10729     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10730     DisplayTitle(buf);
10731     if (first.sendName) {
10732       sprintf(buf, "name %s\n", gameInfo.black);
10733       SendToProgram(buf, &first);
10734     }
10735     if (first.sendTime) {
10736       if (first.useColors) {
10737         SendToProgram("black\n", &first); /*gnu kludge*/
10738       }
10739       SendTimeRemaining(&first, TRUE);
10740     }
10741     if (first.useColors) {
10742       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10743     }
10744     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10745     SetMachineThinkingEnables();
10746     first.maybeThinking = TRUE;
10747     StartClocks();
10748     firstMove = FALSE;
10749
10750     if (appData.autoFlipView && !flipView) {
10751       flipView = !flipView;
10752       DrawPosition(FALSE, NULL);
10753       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10754     }
10755
10756     if(bookHit) { // [HGM] book: simulate book reply
10757         static char bookMove[MSG_SIZ]; // a bit generous?
10758
10759         programStats.nodes = programStats.depth = programStats.time = 
10760         programStats.score = programStats.got_only_move = 0;
10761         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10762
10763         strcpy(bookMove, "move ");
10764         strcat(bookMove, bookHit);
10765         HandleMachineMove(bookMove, &first);
10766     }
10767 }
10768
10769 void
10770 MachineBlackEvent()
10771 {
10772     char buf[MSG_SIZ];
10773    char *bookHit = NULL;
10774
10775     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10776         return;
10777
10778
10779     if (gameMode == PlayFromGameFile || 
10780         gameMode == TwoMachinesPlay  || 
10781         gameMode == Training         || 
10782         gameMode == AnalyzeMode      || 
10783         gameMode == EndOfGame)
10784         EditGameEvent();
10785
10786     if (gameMode == EditPosition) 
10787         EditPositionDone();
10788
10789     if (WhiteOnMove(currentMove)) {
10790         DisplayError(_("It is not Black's turn"), 0);
10791         return;
10792     }
10793     
10794     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10795       ExitAnalyzeMode();
10796
10797     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10798         gameMode == AnalyzeFile)
10799         TruncateGame();
10800
10801     ResurrectChessProgram();    /* in case it isn't running */
10802     gameMode = MachinePlaysBlack;
10803     pausing = FALSE;
10804     ModeHighlight();
10805     SetGameInfo();
10806     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10807     DisplayTitle(buf);
10808     if (first.sendName) {
10809       sprintf(buf, "name %s\n", gameInfo.white);
10810       SendToProgram(buf, &first);
10811     }
10812     if (first.sendTime) {
10813       if (first.useColors) {
10814         SendToProgram("white\n", &first); /*gnu kludge*/
10815       }
10816       SendTimeRemaining(&first, FALSE);
10817     }
10818     if (first.useColors) {
10819       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10820     }
10821     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10822     SetMachineThinkingEnables();
10823     first.maybeThinking = TRUE;
10824     StartClocks();
10825
10826     if (appData.autoFlipView && flipView) {
10827       flipView = !flipView;
10828       DrawPosition(FALSE, NULL);
10829       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10830     }
10831     if(bookHit) { // [HGM] book: simulate book reply
10832         static char bookMove[MSG_SIZ]; // a bit generous?
10833
10834         programStats.nodes = programStats.depth = programStats.time = 
10835         programStats.score = programStats.got_only_move = 0;
10836         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10837
10838         strcpy(bookMove, "move ");
10839         strcat(bookMove, bookHit);
10840         HandleMachineMove(bookMove, &first);
10841     }
10842 }
10843
10844
10845 void
10846 DisplayTwoMachinesTitle()
10847 {
10848     char buf[MSG_SIZ];
10849     if (appData.matchGames > 0) {
10850         if (first.twoMachinesColor[0] == 'w') {
10851             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10852                     gameInfo.white, gameInfo.black,
10853                     first.matchWins, second.matchWins,
10854                     matchGame - 1 - (first.matchWins + second.matchWins));
10855         } else {
10856             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10857                     gameInfo.white, gameInfo.black,
10858                     second.matchWins, first.matchWins,
10859                     matchGame - 1 - (first.matchWins + second.matchWins));
10860         }
10861     } else {
10862         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10863     }
10864     DisplayTitle(buf);
10865 }
10866
10867 void
10868 TwoMachinesEvent P((void))
10869 {
10870     int i;
10871     char buf[MSG_SIZ];
10872     ChessProgramState *onmove;
10873     char *bookHit = NULL;
10874     
10875     if (appData.noChessProgram) return;
10876
10877     switch (gameMode) {
10878       case TwoMachinesPlay:
10879         return;
10880       case MachinePlaysWhite:
10881       case MachinePlaysBlack:
10882         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10883             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10884             return;
10885         }
10886         /* fall through */
10887       case BeginningOfGame:
10888       case PlayFromGameFile:
10889       case EndOfGame:
10890         EditGameEvent();
10891         if (gameMode != EditGame) return;
10892         break;
10893       case EditPosition:
10894         EditPositionDone();
10895         break;
10896       case AnalyzeMode:
10897       case AnalyzeFile:
10898         ExitAnalyzeMode();
10899         break;
10900       case EditGame:
10901       default:
10902         break;
10903     }
10904
10905     forwardMostMove = currentMove;
10906     ResurrectChessProgram();    /* in case first program isn't running */
10907
10908     if (second.pr == NULL) {
10909         StartChessProgram(&second);
10910         if (second.protocolVersion == 1) {
10911           TwoMachinesEventIfReady();
10912         } else {
10913           /* kludge: allow timeout for initial "feature" command */
10914           FreezeUI();
10915           DisplayMessage("", _("Starting second chess program"));
10916           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10917         }
10918         return;
10919     }
10920     DisplayMessage("", "");
10921     InitChessProgram(&second, FALSE);
10922     SendToProgram("force\n", &second);
10923     if (startedFromSetupPosition) {
10924         SendBoard(&second, backwardMostMove);
10925     if (appData.debugMode) {
10926         fprintf(debugFP, "Two Machines\n");
10927     }
10928     }
10929     for (i = backwardMostMove; i < forwardMostMove; i++) {
10930         SendMoveToProgram(i, &second);
10931     }
10932
10933     gameMode = TwoMachinesPlay;
10934     pausing = FALSE;
10935     ModeHighlight();
10936     SetGameInfo();
10937     DisplayTwoMachinesTitle();
10938     firstMove = TRUE;
10939     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10940         onmove = &first;
10941     } else {
10942         onmove = &second;
10943     }
10944
10945     SendToProgram(first.computerString, &first);
10946     if (first.sendName) {
10947       sprintf(buf, "name %s\n", second.tidy);
10948       SendToProgram(buf, &first);
10949     }
10950     SendToProgram(second.computerString, &second);
10951     if (second.sendName) {
10952       sprintf(buf, "name %s\n", first.tidy);
10953       SendToProgram(buf, &second);
10954     }
10955
10956     ResetClocks();
10957     if (!first.sendTime || !second.sendTime) {
10958         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10959         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10960     }
10961     if (onmove->sendTime) {
10962       if (onmove->useColors) {
10963         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10964       }
10965       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10966     }
10967     if (onmove->useColors) {
10968       SendToProgram(onmove->twoMachinesColor, onmove);
10969     }
10970     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10971 //    SendToProgram("go\n", onmove);
10972     onmove->maybeThinking = TRUE;
10973     SetMachineThinkingEnables();
10974
10975     StartClocks();
10976
10977     if(bookHit) { // [HGM] book: simulate book reply
10978         static char bookMove[MSG_SIZ]; // a bit generous?
10979
10980         programStats.nodes = programStats.depth = programStats.time = 
10981         programStats.score = programStats.got_only_move = 0;
10982         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10983
10984         strcpy(bookMove, "move ");
10985         strcat(bookMove, bookHit);
10986         savedMessage = bookMove; // args for deferred call
10987         savedState = onmove;
10988         ScheduleDelayedEvent(DeferredBookMove, 1);
10989     }
10990 }
10991
10992 void
10993 TrainingEvent()
10994 {
10995     if (gameMode == Training) {
10996       SetTrainingModeOff();
10997       gameMode = PlayFromGameFile;
10998       DisplayMessage("", _("Training mode off"));
10999     } else {
11000       gameMode = Training;
11001       animateTraining = appData.animate;
11002
11003       /* make sure we are not already at the end of the game */
11004       if (currentMove < forwardMostMove) {
11005         SetTrainingModeOn();
11006         DisplayMessage("", _("Training mode on"));
11007       } else {
11008         gameMode = PlayFromGameFile;
11009         DisplayError(_("Already at end of game"), 0);
11010       }
11011     }
11012     ModeHighlight();
11013 }
11014
11015 void
11016 IcsClientEvent()
11017 {
11018     if (!appData.icsActive) return;
11019     switch (gameMode) {
11020       case IcsPlayingWhite:
11021       case IcsPlayingBlack:
11022       case IcsObserving:
11023       case IcsIdle:
11024       case BeginningOfGame:
11025       case IcsExamining:
11026         return;
11027
11028       case EditGame:
11029         break;
11030
11031       case EditPosition:
11032         EditPositionDone();
11033         break;
11034
11035       case AnalyzeMode:
11036       case AnalyzeFile:
11037         ExitAnalyzeMode();
11038         break;
11039         
11040       default:
11041         EditGameEvent();
11042         break;
11043     }
11044
11045     gameMode = IcsIdle;
11046     ModeHighlight();
11047     return;
11048 }
11049
11050
11051 void
11052 EditGameEvent()
11053 {
11054     int i;
11055
11056     switch (gameMode) {
11057       case Training:
11058         SetTrainingModeOff();
11059         break;
11060       case MachinePlaysWhite:
11061       case MachinePlaysBlack:
11062       case BeginningOfGame:
11063         SendToProgram("force\n", &first);
11064         SetUserThinkingEnables();
11065         break;
11066       case PlayFromGameFile:
11067         (void) StopLoadGameTimer();
11068         if (gameFileFP != NULL) {
11069             gameFileFP = NULL;
11070         }
11071         break;
11072       case EditPosition:
11073         EditPositionDone();
11074         break;
11075       case AnalyzeMode:
11076       case AnalyzeFile:
11077         ExitAnalyzeMode();
11078         SendToProgram("force\n", &first);
11079         break;
11080       case TwoMachinesPlay:
11081         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11082         ResurrectChessProgram();
11083         SetUserThinkingEnables();
11084         break;
11085       case EndOfGame:
11086         ResurrectChessProgram();
11087         break;
11088       case IcsPlayingBlack:
11089       case IcsPlayingWhite:
11090         DisplayError(_("Warning: You are still playing a game"), 0);
11091         break;
11092       case IcsObserving:
11093         DisplayError(_("Warning: You are still observing a game"), 0);
11094         break;
11095       case IcsExamining:
11096         DisplayError(_("Warning: You are still examining a game"), 0);
11097         break;
11098       case IcsIdle:
11099         break;
11100       case EditGame:
11101       default:
11102         return;
11103     }
11104     
11105     pausing = FALSE;
11106     StopClocks();
11107     first.offeredDraw = second.offeredDraw = 0;
11108
11109     if (gameMode == PlayFromGameFile) {
11110         whiteTimeRemaining = timeRemaining[0][currentMove];
11111         blackTimeRemaining = timeRemaining[1][currentMove];
11112         DisplayTitle("");
11113     }
11114
11115     if (gameMode == MachinePlaysWhite ||
11116         gameMode == MachinePlaysBlack ||
11117         gameMode == TwoMachinesPlay ||
11118         gameMode == EndOfGame) {
11119         i = forwardMostMove;
11120         while (i > currentMove) {
11121             SendToProgram("undo\n", &first);
11122             i--;
11123         }
11124         whiteTimeRemaining = timeRemaining[0][currentMove];
11125         blackTimeRemaining = timeRemaining[1][currentMove];
11126         DisplayBothClocks();
11127         if (whiteFlag || blackFlag) {
11128             whiteFlag = blackFlag = 0;
11129         }
11130         DisplayTitle("");
11131     }           
11132     
11133     gameMode = EditGame;
11134     ModeHighlight();
11135     SetGameInfo();
11136 }
11137
11138
11139 void
11140 EditPositionEvent()
11141 {
11142     if (gameMode == EditPosition) {
11143         EditGameEvent();
11144         return;
11145     }
11146     
11147     EditGameEvent();
11148     if (gameMode != EditGame) return;
11149     
11150     gameMode = EditPosition;
11151     ModeHighlight();
11152     SetGameInfo();
11153     if (currentMove > 0)
11154       CopyBoard(boards[0], boards[currentMove]);
11155     
11156     blackPlaysFirst = !WhiteOnMove(currentMove);
11157     ResetClocks();
11158     currentMove = forwardMostMove = backwardMostMove = 0;
11159     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11160     DisplayMove(-1);
11161 }
11162
11163 void
11164 ExitAnalyzeMode()
11165 {
11166     /* [DM] icsEngineAnalyze - possible call from other functions */
11167     if (appData.icsEngineAnalyze) {
11168         appData.icsEngineAnalyze = FALSE;
11169
11170         DisplayMessage("",_("Close ICS engine analyze..."));
11171     }
11172     if (first.analysisSupport && first.analyzing) {
11173       SendToProgram("exit\n", &first);
11174       first.analyzing = FALSE;
11175     }
11176     thinkOutput[0] = NULLCHAR;
11177 }
11178
11179 void
11180 EditPositionDone()
11181 {
11182     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11183
11184     startedFromSetupPosition = TRUE;
11185     InitChessProgram(&first, FALSE);
11186     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11187     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11188         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11189         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11190     } else castlingRights[0][2] = -1;
11191     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11192         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11193         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11194     } else castlingRights[0][5] = -1;
11195     SendToProgram("force\n", &first);
11196     if (blackPlaysFirst) {
11197         strcpy(moveList[0], "");
11198         strcpy(parseList[0], "");
11199         currentMove = forwardMostMove = backwardMostMove = 1;
11200         CopyBoard(boards[1], boards[0]);
11201         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11202         { int i;
11203           epStatus[1] = epStatus[0];
11204           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11205         }
11206     } else {
11207         currentMove = forwardMostMove = backwardMostMove = 0;
11208     }
11209     SendBoard(&first, forwardMostMove);
11210     if (appData.debugMode) {
11211         fprintf(debugFP, "EditPosDone\n");
11212     }
11213     DisplayTitle("");
11214     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11215     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11216     gameMode = EditGame;
11217     ModeHighlight();
11218     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11219     ClearHighlights(); /* [AS] */
11220 }
11221
11222 /* Pause for `ms' milliseconds */
11223 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11224 void
11225 TimeDelay(ms)
11226      long ms;
11227 {
11228     TimeMark m1, m2;
11229
11230     GetTimeMark(&m1);
11231     do {
11232         GetTimeMark(&m2);
11233     } while (SubtractTimeMarks(&m2, &m1) < ms);
11234 }
11235
11236 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11237 void
11238 SendMultiLineToICS(buf)
11239      char *buf;
11240 {
11241     char temp[MSG_SIZ+1], *p;
11242     int len;
11243
11244     len = strlen(buf);
11245     if (len > MSG_SIZ)
11246       len = MSG_SIZ;
11247   
11248     strncpy(temp, buf, len);
11249     temp[len] = 0;
11250
11251     p = temp;
11252     while (*p) {
11253         if (*p == '\n' || *p == '\r')
11254           *p = ' ';
11255         ++p;
11256     }
11257
11258     strcat(temp, "\n");
11259     SendToICS(temp);
11260     SendToPlayer(temp, strlen(temp));
11261 }
11262
11263 void
11264 SetWhiteToPlayEvent()
11265 {
11266     if (gameMode == EditPosition) {
11267         blackPlaysFirst = FALSE;
11268         DisplayBothClocks();    /* works because currentMove is 0 */
11269     } else if (gameMode == IcsExamining) {
11270         SendToICS(ics_prefix);
11271         SendToICS("tomove white\n");
11272     }
11273 }
11274
11275 void
11276 SetBlackToPlayEvent()
11277 {
11278     if (gameMode == EditPosition) {
11279         blackPlaysFirst = TRUE;
11280         currentMove = 1;        /* kludge */
11281         DisplayBothClocks();
11282         currentMove = 0;
11283     } else if (gameMode == IcsExamining) {
11284         SendToICS(ics_prefix);
11285         SendToICS("tomove black\n");
11286     }
11287 }
11288
11289 void
11290 EditPositionMenuEvent(selection, x, y)
11291      ChessSquare selection;
11292      int x, y;
11293 {
11294     char buf[MSG_SIZ];
11295     ChessSquare piece = boards[0][y][x];
11296
11297     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11298
11299     switch (selection) {
11300       case ClearBoard:
11301         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11302             SendToICS(ics_prefix);
11303             SendToICS("bsetup clear\n");
11304         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11305             SendToICS(ics_prefix);
11306             SendToICS("clearboard\n");
11307         } else {
11308             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11309                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11310                 for (y = 0; y < BOARD_HEIGHT; y++) {
11311                     if (gameMode == IcsExamining) {
11312                         if (boards[currentMove][y][x] != EmptySquare) {
11313                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11314                                     AAA + x, ONE + y);
11315                             SendToICS(buf);
11316                         }
11317                     } else {
11318                         boards[0][y][x] = p;
11319                     }
11320                 }
11321             }
11322         }
11323         if (gameMode == EditPosition) {
11324             DrawPosition(FALSE, boards[0]);
11325         }
11326         break;
11327
11328       case WhitePlay:
11329         SetWhiteToPlayEvent();
11330         break;
11331
11332       case BlackPlay:
11333         SetBlackToPlayEvent();
11334         break;
11335
11336       case EmptySquare:
11337         if (gameMode == IcsExamining) {
11338             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11339             SendToICS(buf);
11340         } else {
11341             boards[0][y][x] = EmptySquare;
11342             DrawPosition(FALSE, boards[0]);
11343         }
11344         break;
11345
11346       case PromotePiece:
11347         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11348            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11349             selection = (ChessSquare) (PROMOTED piece);
11350         } else if(piece == EmptySquare) selection = WhiteSilver;
11351         else selection = (ChessSquare)((int)piece - 1);
11352         goto defaultlabel;
11353
11354       case DemotePiece:
11355         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11356            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11357             selection = (ChessSquare) (DEMOTED piece);
11358         } else if(piece == EmptySquare) selection = BlackSilver;
11359         else selection = (ChessSquare)((int)piece + 1);       
11360         goto defaultlabel;
11361
11362       case WhiteQueen:
11363       case BlackQueen:
11364         if(gameInfo.variant == VariantShatranj ||
11365            gameInfo.variant == VariantXiangqi  ||
11366            gameInfo.variant == VariantCourier    )
11367             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11368         goto defaultlabel;
11369
11370       case WhiteKing:
11371       case BlackKing:
11372         if(gameInfo.variant == VariantXiangqi)
11373             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11374         if(gameInfo.variant == VariantKnightmate)
11375             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11376       default:
11377         defaultlabel:
11378         if (gameMode == IcsExamining) {
11379             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11380                     PieceToChar(selection), AAA + x, ONE + y);
11381             SendToICS(buf);
11382         } else {
11383             boards[0][y][x] = selection;
11384             DrawPosition(FALSE, boards[0]);
11385         }
11386         break;
11387     }
11388 }
11389
11390
11391 void
11392 DropMenuEvent(selection, x, y)
11393      ChessSquare selection;
11394      int x, y;
11395 {
11396     ChessMove moveType;
11397
11398     switch (gameMode) {
11399       case IcsPlayingWhite:
11400       case MachinePlaysBlack:
11401         if (!WhiteOnMove(currentMove)) {
11402             DisplayMoveError(_("It is Black's turn"));
11403             return;
11404         }
11405         moveType = WhiteDrop;
11406         break;
11407       case IcsPlayingBlack:
11408       case MachinePlaysWhite:
11409         if (WhiteOnMove(currentMove)) {
11410             DisplayMoveError(_("It is White's turn"));
11411             return;
11412         }
11413         moveType = BlackDrop;
11414         break;
11415       case EditGame:
11416         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11417         break;
11418       default:
11419         return;
11420     }
11421
11422     if (moveType == BlackDrop && selection < BlackPawn) {
11423       selection = (ChessSquare) ((int) selection
11424                                  + (int) BlackPawn - (int) WhitePawn);
11425     }
11426     if (boards[currentMove][y][x] != EmptySquare) {
11427         DisplayMoveError(_("That square is occupied"));
11428         return;
11429     }
11430
11431     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11432 }
11433
11434 void
11435 AcceptEvent()
11436 {
11437     /* Accept a pending offer of any kind from opponent */
11438     
11439     if (appData.icsActive) {
11440         SendToICS(ics_prefix);
11441         SendToICS("accept\n");
11442     } else if (cmailMsgLoaded) {
11443         if (currentMove == cmailOldMove &&
11444             commentList[cmailOldMove] != NULL &&
11445             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11446                    "Black offers a draw" : "White offers a draw")) {
11447             TruncateGame();
11448             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11449             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11450         } else {
11451             DisplayError(_("There is no pending offer on this move"), 0);
11452             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11453         }
11454     } else {
11455         /* Not used for offers from chess program */
11456     }
11457 }
11458
11459 void
11460 DeclineEvent()
11461 {
11462     /* Decline a pending offer of any kind from opponent */
11463     
11464     if (appData.icsActive) {
11465         SendToICS(ics_prefix);
11466         SendToICS("decline\n");
11467     } else if (cmailMsgLoaded) {
11468         if (currentMove == cmailOldMove &&
11469             commentList[cmailOldMove] != NULL &&
11470             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11471                    "Black offers a draw" : "White offers a draw")) {
11472 #ifdef NOTDEF
11473             AppendComment(cmailOldMove, "Draw declined");
11474             DisplayComment(cmailOldMove - 1, "Draw declined");
11475 #endif /*NOTDEF*/
11476         } else {
11477             DisplayError(_("There is no pending offer on this move"), 0);
11478         }
11479     } else {
11480         /* Not used for offers from chess program */
11481     }
11482 }
11483
11484 void
11485 RematchEvent()
11486 {
11487     /* Issue ICS rematch command */
11488     if (appData.icsActive) {
11489         SendToICS(ics_prefix);
11490         SendToICS("rematch\n");
11491     }
11492 }
11493
11494 void
11495 CallFlagEvent()
11496 {
11497     /* Call your opponent's flag (claim a win on time) */
11498     if (appData.icsActive) {
11499         SendToICS(ics_prefix);
11500         SendToICS("flag\n");
11501     } else {
11502         switch (gameMode) {
11503           default:
11504             return;
11505           case MachinePlaysWhite:
11506             if (whiteFlag) {
11507                 if (blackFlag)
11508                   GameEnds(GameIsDrawn, "Both players ran out of time",
11509                            GE_PLAYER);
11510                 else
11511                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11512             } else {
11513                 DisplayError(_("Your opponent is not out of time"), 0);
11514             }
11515             break;
11516           case MachinePlaysBlack:
11517             if (blackFlag) {
11518                 if (whiteFlag)
11519                   GameEnds(GameIsDrawn, "Both players ran out of time",
11520                            GE_PLAYER);
11521                 else
11522                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11523             } else {
11524                 DisplayError(_("Your opponent is not out of time"), 0);
11525             }
11526             break;
11527         }
11528     }
11529 }
11530
11531 void
11532 DrawEvent()
11533 {
11534     /* Offer draw or accept pending draw offer from opponent */
11535     
11536     if (appData.icsActive) {
11537         /* Note: tournament rules require draw offers to be
11538            made after you make your move but before you punch
11539            your clock.  Currently ICS doesn't let you do that;
11540            instead, you immediately punch your clock after making
11541            a move, but you can offer a draw at any time. */
11542         
11543         SendToICS(ics_prefix);
11544         SendToICS("draw\n");
11545     } else if (cmailMsgLoaded) {
11546         if (currentMove == cmailOldMove &&
11547             commentList[cmailOldMove] != NULL &&
11548             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11549                    "Black offers a draw" : "White offers a draw")) {
11550             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11551             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11552         } else if (currentMove == cmailOldMove + 1) {
11553             char *offer = WhiteOnMove(cmailOldMove) ?
11554               "White offers a draw" : "Black offers a draw";
11555             AppendComment(currentMove, offer);
11556             DisplayComment(currentMove - 1, offer);
11557             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11558         } else {
11559             DisplayError(_("You must make your move before offering a draw"), 0);
11560             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11561         }
11562     } else if (first.offeredDraw) {
11563         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11564     } else {
11565         if (first.sendDrawOffers) {
11566             SendToProgram("draw\n", &first);
11567             userOfferedDraw = TRUE;
11568         }
11569     }
11570 }
11571
11572 void
11573 AdjournEvent()
11574 {
11575     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11576     
11577     if (appData.icsActive) {
11578         SendToICS(ics_prefix);
11579         SendToICS("adjourn\n");
11580     } else {
11581         /* Currently GNU Chess doesn't offer or accept Adjourns */
11582     }
11583 }
11584
11585
11586 void
11587 AbortEvent()
11588 {
11589     /* Offer Abort or accept pending Abort offer from opponent */
11590     
11591     if (appData.icsActive) {
11592         SendToICS(ics_prefix);
11593         SendToICS("abort\n");
11594     } else {
11595         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11596     }
11597 }
11598
11599 void
11600 ResignEvent()
11601 {
11602     /* Resign.  You can do this even if it's not your turn. */
11603     
11604     if (appData.icsActive) {
11605         SendToICS(ics_prefix);
11606         SendToICS("resign\n");
11607     } else {
11608         switch (gameMode) {
11609           case MachinePlaysWhite:
11610             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11611             break;
11612           case MachinePlaysBlack:
11613             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11614             break;
11615           case EditGame:
11616             if (cmailMsgLoaded) {
11617                 TruncateGame();
11618                 if (WhiteOnMove(cmailOldMove)) {
11619                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11620                 } else {
11621                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11622                 }
11623                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11624             }
11625             break;
11626           default:
11627             break;
11628         }
11629     }
11630 }
11631
11632
11633 void
11634 StopObservingEvent()
11635 {
11636     /* Stop observing current games */
11637     SendToICS(ics_prefix);
11638     SendToICS("unobserve\n");
11639 }
11640
11641 void
11642 StopExaminingEvent()
11643 {
11644     /* Stop observing current game */
11645     SendToICS(ics_prefix);
11646     SendToICS("unexamine\n");
11647 }
11648
11649 void
11650 ForwardInner(target)
11651      int target;
11652 {
11653     int limit;
11654
11655     if (appData.debugMode)
11656         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11657                 target, currentMove, forwardMostMove);
11658
11659     if (gameMode == EditPosition)
11660       return;
11661
11662     if (gameMode == PlayFromGameFile && !pausing)
11663       PauseEvent();
11664     
11665     if (gameMode == IcsExamining && pausing)
11666       limit = pauseExamForwardMostMove;
11667     else
11668       limit = forwardMostMove;
11669     
11670     if (target > limit) target = limit;
11671
11672     if (target > 0 && moveList[target - 1][0]) {
11673         int fromX, fromY, toX, toY;
11674         toX = moveList[target - 1][2] - AAA;
11675         toY = moveList[target - 1][3] - ONE;
11676         if (moveList[target - 1][1] == '@') {
11677             if (appData.highlightLastMove) {
11678                 SetHighlights(-1, -1, toX, toY);
11679             }
11680         } else {
11681             fromX = moveList[target - 1][0] - AAA;
11682             fromY = moveList[target - 1][1] - ONE;
11683             if (target == currentMove + 1) {
11684                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11685             }
11686             if (appData.highlightLastMove) {
11687                 SetHighlights(fromX, fromY, toX, toY);
11688             }
11689         }
11690     }
11691     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11692         gameMode == Training || gameMode == PlayFromGameFile || 
11693         gameMode == AnalyzeFile) {
11694         while (currentMove < target) {
11695             SendMoveToProgram(currentMove++, &first);
11696         }
11697     } else {
11698         currentMove = target;
11699     }
11700     
11701     if (gameMode == EditGame || gameMode == EndOfGame) {
11702         whiteTimeRemaining = timeRemaining[0][currentMove];
11703         blackTimeRemaining = timeRemaining[1][currentMove];
11704     }
11705     DisplayBothClocks();
11706     DisplayMove(currentMove - 1);
11707     DrawPosition(FALSE, boards[currentMove]);
11708     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11709     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11710         DisplayComment(currentMove - 1, commentList[currentMove]);
11711     }
11712 }
11713
11714
11715 void
11716 ForwardEvent()
11717 {
11718     if (gameMode == IcsExamining && !pausing) {
11719         SendToICS(ics_prefix);
11720         SendToICS("forward\n");
11721     } else {
11722         ForwardInner(currentMove + 1);
11723     }
11724 }
11725
11726 void
11727 ToEndEvent()
11728 {
11729     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11730         /* to optimze, we temporarily turn off analysis mode while we feed
11731          * the remaining moves to the engine. Otherwise we get analysis output
11732          * after each move.
11733          */ 
11734         if (first.analysisSupport) {
11735           SendToProgram("exit\nforce\n", &first);
11736           first.analyzing = FALSE;
11737         }
11738     }
11739         
11740     if (gameMode == IcsExamining && !pausing) {
11741         SendToICS(ics_prefix);
11742         SendToICS("forward 999999\n");
11743     } else {
11744         ForwardInner(forwardMostMove);
11745     }
11746
11747     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11748         /* we have fed all the moves, so reactivate analysis mode */
11749         SendToProgram("analyze\n", &first);
11750         first.analyzing = TRUE;
11751         /*first.maybeThinking = TRUE;*/
11752         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11753     }
11754 }
11755
11756 void
11757 BackwardInner(target)
11758      int target;
11759 {
11760     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11761
11762     if (appData.debugMode)
11763         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11764                 target, currentMove, forwardMostMove);
11765
11766     if (gameMode == EditPosition) return;
11767     if (currentMove <= backwardMostMove) {
11768         ClearHighlights();
11769         DrawPosition(full_redraw, boards[currentMove]);
11770         return;
11771     }
11772     if (gameMode == PlayFromGameFile && !pausing)
11773       PauseEvent();
11774     
11775     if (moveList[target][0]) {
11776         int fromX, fromY, toX, toY;
11777         toX = moveList[target][2] - AAA;
11778         toY = moveList[target][3] - ONE;
11779         if (moveList[target][1] == '@') {
11780             if (appData.highlightLastMove) {
11781                 SetHighlights(-1, -1, toX, toY);
11782             }
11783         } else {
11784             fromX = moveList[target][0] - AAA;
11785             fromY = moveList[target][1] - ONE;
11786             if (target == currentMove - 1) {
11787                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11788             }
11789             if (appData.highlightLastMove) {
11790                 SetHighlights(fromX, fromY, toX, toY);
11791             }
11792         }
11793     }
11794     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11795         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11796         while (currentMove > target) {
11797             SendToProgram("undo\n", &first);
11798             currentMove--;
11799         }
11800     } else {
11801         currentMove = target;
11802     }
11803     
11804     if (gameMode == EditGame || gameMode == EndOfGame) {
11805         whiteTimeRemaining = timeRemaining[0][currentMove];
11806         blackTimeRemaining = timeRemaining[1][currentMove];
11807     }
11808     DisplayBothClocks();
11809     DisplayMove(currentMove - 1);
11810     DrawPosition(full_redraw, boards[currentMove]);
11811     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11812     // [HGM] PV info: routine tests if comment empty
11813     DisplayComment(currentMove - 1, commentList[currentMove]);
11814 }
11815
11816 void
11817 BackwardEvent()
11818 {
11819     if (gameMode == IcsExamining && !pausing) {
11820         SendToICS(ics_prefix);
11821         SendToICS("backward\n");
11822     } else {
11823         BackwardInner(currentMove - 1);
11824     }
11825 }
11826
11827 void
11828 ToStartEvent()
11829 {
11830     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11831         /* to optimze, we temporarily turn off analysis mode while we undo
11832          * all the moves. Otherwise we get analysis output after each undo.
11833          */ 
11834         if (first.analysisSupport) {
11835           SendToProgram("exit\nforce\n", &first);
11836           first.analyzing = FALSE;
11837         }
11838     }
11839
11840     if (gameMode == IcsExamining && !pausing) {
11841         SendToICS(ics_prefix);
11842         SendToICS("backward 999999\n");
11843     } else {
11844         BackwardInner(backwardMostMove);
11845     }
11846
11847     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11848         /* we have fed all the moves, so reactivate analysis mode */
11849         SendToProgram("analyze\n", &first);
11850         first.analyzing = TRUE;
11851         /*first.maybeThinking = TRUE;*/
11852         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11853     }
11854 }
11855
11856 void
11857 ToNrEvent(int to)
11858 {
11859   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11860   if (to >= forwardMostMove) to = forwardMostMove;
11861   if (to <= backwardMostMove) to = backwardMostMove;
11862   if (to < currentMove) {
11863     BackwardInner(to);
11864   } else {
11865     ForwardInner(to);
11866   }
11867 }
11868
11869 void
11870 RevertEvent()
11871 {
11872     if (gameMode != IcsExamining) {
11873         DisplayError(_("You are not examining a game"), 0);
11874         return;
11875     }
11876     if (pausing) {
11877         DisplayError(_("You can't revert while pausing"), 0);
11878         return;
11879     }
11880     SendToICS(ics_prefix);
11881     SendToICS("revert\n");
11882 }
11883
11884 void
11885 RetractMoveEvent()
11886 {
11887     switch (gameMode) {
11888       case MachinePlaysWhite:
11889       case MachinePlaysBlack:
11890         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11891             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11892             return;
11893         }
11894         if (forwardMostMove < 2) return;
11895         currentMove = forwardMostMove = forwardMostMove - 2;
11896         whiteTimeRemaining = timeRemaining[0][currentMove];
11897         blackTimeRemaining = timeRemaining[1][currentMove];
11898         DisplayBothClocks();
11899         DisplayMove(currentMove - 1);
11900         ClearHighlights();/*!! could figure this out*/
11901         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11902         SendToProgram("remove\n", &first);
11903         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11904         break;
11905
11906       case BeginningOfGame:
11907       default:
11908         break;
11909
11910       case IcsPlayingWhite:
11911       case IcsPlayingBlack:
11912         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11913             SendToICS(ics_prefix);
11914             SendToICS("takeback 2\n");
11915         } else {
11916             SendToICS(ics_prefix);
11917             SendToICS("takeback 1\n");
11918         }
11919         break;
11920     }
11921 }
11922
11923 void
11924 MoveNowEvent()
11925 {
11926     ChessProgramState *cps;
11927
11928     switch (gameMode) {
11929       case MachinePlaysWhite:
11930         if (!WhiteOnMove(forwardMostMove)) {
11931             DisplayError(_("It is your turn"), 0);
11932             return;
11933         }
11934         cps = &first;
11935         break;
11936       case MachinePlaysBlack:
11937         if (WhiteOnMove(forwardMostMove)) {
11938             DisplayError(_("It is your turn"), 0);
11939             return;
11940         }
11941         cps = &first;
11942         break;
11943       case TwoMachinesPlay:
11944         if (WhiteOnMove(forwardMostMove) ==
11945             (first.twoMachinesColor[0] == 'w')) {
11946             cps = &first;
11947         } else {
11948             cps = &second;
11949         }
11950         break;
11951       case BeginningOfGame:
11952       default:
11953         return;
11954     }
11955     SendToProgram("?\n", cps);
11956 }
11957
11958 void
11959 TruncateGameEvent()
11960 {
11961     EditGameEvent();
11962     if (gameMode != EditGame) return;
11963     TruncateGame();
11964 }
11965
11966 void
11967 TruncateGame()
11968 {
11969     if (forwardMostMove > currentMove) {
11970         if (gameInfo.resultDetails != NULL) {
11971             free(gameInfo.resultDetails);
11972             gameInfo.resultDetails = NULL;
11973             gameInfo.result = GameUnfinished;
11974         }
11975         forwardMostMove = currentMove;
11976         HistorySet(parseList, backwardMostMove, forwardMostMove,
11977                    currentMove-1);
11978     }
11979 }
11980
11981 void
11982 HintEvent()
11983 {
11984     if (appData.noChessProgram) return;
11985     switch (gameMode) {
11986       case MachinePlaysWhite:
11987         if (WhiteOnMove(forwardMostMove)) {
11988             DisplayError(_("Wait until your turn"), 0);
11989             return;
11990         }
11991         break;
11992       case BeginningOfGame:
11993       case MachinePlaysBlack:
11994         if (!WhiteOnMove(forwardMostMove)) {
11995             DisplayError(_("Wait until your turn"), 0);
11996             return;
11997         }
11998         break;
11999       default:
12000         DisplayError(_("No hint available"), 0);
12001         return;
12002     }
12003     SendToProgram("hint\n", &first);
12004     hintRequested = TRUE;
12005 }
12006
12007 void
12008 BookEvent()
12009 {
12010     if (appData.noChessProgram) return;
12011     switch (gameMode) {
12012       case MachinePlaysWhite:
12013         if (WhiteOnMove(forwardMostMove)) {
12014             DisplayError(_("Wait until your turn"), 0);
12015             return;
12016         }
12017         break;
12018       case BeginningOfGame:
12019       case MachinePlaysBlack:
12020         if (!WhiteOnMove(forwardMostMove)) {
12021             DisplayError(_("Wait until your turn"), 0);
12022             return;
12023         }
12024         break;
12025       case EditPosition:
12026         EditPositionDone();
12027         break;
12028       case TwoMachinesPlay:
12029         return;
12030       default:
12031         break;
12032     }
12033     SendToProgram("bk\n", &first);
12034     bookOutput[0] = NULLCHAR;
12035     bookRequested = TRUE;
12036 }
12037
12038 void
12039 AboutGameEvent()
12040 {
12041     char *tags = PGNTags(&gameInfo);
12042     TagsPopUp(tags, CmailMsg());
12043     free(tags);
12044 }
12045
12046 /* end button procedures */
12047
12048 void
12049 PrintPosition(fp, move)
12050      FILE *fp;
12051      int move;
12052 {
12053     int i, j;
12054     
12055     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12056         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12057             char c = PieceToChar(boards[move][i][j]);
12058             fputc(c == 'x' ? '.' : c, fp);
12059             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12060         }
12061     }
12062     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12063       fprintf(fp, "white to play\n");
12064     else
12065       fprintf(fp, "black to play\n");
12066 }
12067
12068 void
12069 PrintOpponents(fp)
12070      FILE *fp;
12071 {
12072     if (gameInfo.white != NULL) {
12073         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12074     } else {
12075         fprintf(fp, "\n");
12076     }
12077 }
12078
12079 /* Find last component of program's own name, using some heuristics */
12080 void
12081 TidyProgramName(prog, host, buf)
12082      char *prog, *host, buf[MSG_SIZ];
12083 {
12084     char *p, *q;
12085     int local = (strcmp(host, "localhost") == 0);
12086     while (!local && (p = strchr(prog, ';')) != NULL) {
12087         p++;
12088         while (*p == ' ') p++;
12089         prog = p;
12090     }
12091     if (*prog == '"' || *prog == '\'') {
12092         q = strchr(prog + 1, *prog);
12093     } else {
12094         q = strchr(prog, ' ');
12095     }
12096     if (q == NULL) q = prog + strlen(prog);
12097     p = q;
12098     while (p >= prog && *p != '/' && *p != '\\') p--;
12099     p++;
12100     if(p == prog && *p == '"') p++;
12101     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12102     memcpy(buf, p, q - p);
12103     buf[q - p] = NULLCHAR;
12104     if (!local) {
12105         strcat(buf, "@");
12106         strcat(buf, host);
12107     }
12108 }
12109
12110 char *
12111 TimeControlTagValue()
12112 {
12113     char buf[MSG_SIZ];
12114     if (!appData.clockMode) {
12115         strcpy(buf, "-");
12116     } else if (movesPerSession > 0) {
12117         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12118     } else if (timeIncrement == 0) {
12119         sprintf(buf, "%ld", timeControl/1000);
12120     } else {
12121         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12122     }
12123     return StrSave(buf);
12124 }
12125
12126 void
12127 SetGameInfo()
12128 {
12129     /* This routine is used only for certain modes */
12130     VariantClass v = gameInfo.variant;
12131     ClearGameInfo(&gameInfo);
12132     gameInfo.variant = v;
12133
12134     switch (gameMode) {
12135       case MachinePlaysWhite:
12136         gameInfo.event = StrSave( appData.pgnEventHeader );
12137         gameInfo.site = StrSave(HostName());
12138         gameInfo.date = PGNDate();
12139         gameInfo.round = StrSave("-");
12140         gameInfo.white = StrSave(first.tidy);
12141         gameInfo.black = StrSave(UserName());
12142         gameInfo.timeControl = TimeControlTagValue();
12143         break;
12144
12145       case MachinePlaysBlack:
12146         gameInfo.event = StrSave( appData.pgnEventHeader );
12147         gameInfo.site = StrSave(HostName());
12148         gameInfo.date = PGNDate();
12149         gameInfo.round = StrSave("-");
12150         gameInfo.white = StrSave(UserName());
12151         gameInfo.black = StrSave(first.tidy);
12152         gameInfo.timeControl = TimeControlTagValue();
12153         break;
12154
12155       case TwoMachinesPlay:
12156         gameInfo.event = StrSave( appData.pgnEventHeader );
12157         gameInfo.site = StrSave(HostName());
12158         gameInfo.date = PGNDate();
12159         if (matchGame > 0) {
12160             char buf[MSG_SIZ];
12161             sprintf(buf, "%d", matchGame);
12162             gameInfo.round = StrSave(buf);
12163         } else {
12164             gameInfo.round = StrSave("-");
12165         }
12166         if (first.twoMachinesColor[0] == 'w') {
12167             gameInfo.white = StrSave(first.tidy);
12168             gameInfo.black = StrSave(second.tidy);
12169         } else {
12170             gameInfo.white = StrSave(second.tidy);
12171             gameInfo.black = StrSave(first.tidy);
12172         }
12173         gameInfo.timeControl = TimeControlTagValue();
12174         break;
12175
12176       case EditGame:
12177         gameInfo.event = StrSave("Edited game");
12178         gameInfo.site = StrSave(HostName());
12179         gameInfo.date = PGNDate();
12180         gameInfo.round = StrSave("-");
12181         gameInfo.white = StrSave("-");
12182         gameInfo.black = StrSave("-");
12183         break;
12184
12185       case EditPosition:
12186         gameInfo.event = StrSave("Edited position");
12187         gameInfo.site = StrSave(HostName());
12188         gameInfo.date = PGNDate();
12189         gameInfo.round = StrSave("-");
12190         gameInfo.white = StrSave("-");
12191         gameInfo.black = StrSave("-");
12192         break;
12193
12194       case IcsPlayingWhite:
12195       case IcsPlayingBlack:
12196       case IcsObserving:
12197       case IcsExamining:
12198         break;
12199
12200       case PlayFromGameFile:
12201         gameInfo.event = StrSave("Game from non-PGN file");
12202         gameInfo.site = StrSave(HostName());
12203         gameInfo.date = PGNDate();
12204         gameInfo.round = StrSave("-");
12205         gameInfo.white = StrSave("?");
12206         gameInfo.black = StrSave("?");
12207         break;
12208
12209       default:
12210         break;
12211     }
12212 }
12213
12214 void
12215 ReplaceComment(index, text)
12216      int index;
12217      char *text;
12218 {
12219     int len;
12220
12221     while (*text == '\n') text++;
12222     len = strlen(text);
12223     while (len > 0 && text[len - 1] == '\n') len--;
12224
12225     if (commentList[index] != NULL)
12226       free(commentList[index]);
12227
12228     if (len == 0) {
12229         commentList[index] = NULL;
12230         return;
12231     }
12232     commentList[index] = (char *) malloc(len + 2);
12233     strncpy(commentList[index], text, len);
12234     commentList[index][len] = '\n';
12235     commentList[index][len + 1] = NULLCHAR;
12236 }
12237
12238 void
12239 CrushCRs(text)
12240      char *text;
12241 {
12242   char *p = text;
12243   char *q = text;
12244   char ch;
12245
12246   do {
12247     ch = *p++;
12248     if (ch == '\r') continue;
12249     *q++ = ch;
12250   } while (ch != '\0');
12251 }
12252
12253 void
12254 AppendComment(index, text)
12255      int index;
12256      char *text;
12257 {
12258     int oldlen, len;
12259     char *old;
12260
12261     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12262
12263     CrushCRs(text);
12264     while (*text == '\n') text++;
12265     len = strlen(text);
12266     while (len > 0 && text[len - 1] == '\n') len--;
12267
12268     if (len == 0) return;
12269
12270     if (commentList[index] != NULL) {
12271         old = commentList[index];
12272         oldlen = strlen(old);
12273         commentList[index] = (char *) malloc(oldlen + len + 2);
12274         strcpy(commentList[index], old);
12275         free(old);
12276         strncpy(&commentList[index][oldlen], text, len);
12277         commentList[index][oldlen + len] = '\n';
12278         commentList[index][oldlen + len + 1] = NULLCHAR;
12279     } else {
12280         commentList[index] = (char *) malloc(len + 2);
12281         strncpy(commentList[index], text, len);
12282         commentList[index][len] = '\n';
12283         commentList[index][len + 1] = NULLCHAR;
12284     }
12285 }
12286
12287 static char * FindStr( char * text, char * sub_text )
12288 {
12289     char * result = strstr( text, sub_text );
12290
12291     if( result != NULL ) {
12292         result += strlen( sub_text );
12293     }
12294
12295     return result;
12296 }
12297
12298 /* [AS] Try to extract PV info from PGN comment */
12299 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12300 char *GetInfoFromComment( int index, char * text )
12301 {
12302     char * sep = text;
12303
12304     if( text != NULL && index > 0 ) {
12305         int score = 0;
12306         int depth = 0;
12307         int time = -1, sec = 0, deci;
12308         char * s_eval = FindStr( text, "[%eval " );
12309         char * s_emt = FindStr( text, "[%emt " );
12310
12311         if( s_eval != NULL || s_emt != NULL ) {
12312             /* New style */
12313             char delim;
12314
12315             if( s_eval != NULL ) {
12316                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12317                     return text;
12318                 }
12319
12320                 if( delim != ']' ) {
12321                     return text;
12322                 }
12323             }
12324
12325             if( s_emt != NULL ) {
12326             }
12327         }
12328         else {
12329             /* We expect something like: [+|-]nnn.nn/dd */
12330             int score_lo = 0;
12331
12332             sep = strchr( text, '/' );
12333             if( sep == NULL || sep < (text+4) ) {
12334                 return text;
12335             }
12336
12337             time = -1; sec = -1; deci = -1;
12338             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12339                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12340                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12341                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12342                 return text;
12343             }
12344
12345             if( score_lo < 0 || score_lo >= 100 ) {
12346                 return text;
12347             }
12348
12349             if(sec >= 0) time = 600*time + 10*sec; else
12350             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12351
12352             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12353
12354             /* [HGM] PV time: now locate end of PV info */
12355             while( *++sep >= '0' && *sep <= '9'); // strip depth
12356             if(time >= 0)
12357             while( *++sep >= '0' && *sep <= '9'); // strip time
12358             if(sec >= 0)
12359             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12360             if(deci >= 0)
12361             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12362             while(*sep == ' ') sep++;
12363         }
12364
12365         if( depth <= 0 ) {
12366             return text;
12367         }
12368
12369         if( time < 0 ) {
12370             time = -1;
12371         }
12372
12373         pvInfoList[index-1].depth = depth;
12374         pvInfoList[index-1].score = score;
12375         pvInfoList[index-1].time  = 10*time; // centi-sec
12376     }
12377     return sep;
12378 }
12379
12380 void
12381 SendToProgram(message, cps)
12382      char *message;
12383      ChessProgramState *cps;
12384 {
12385     int count, outCount, error;
12386     char buf[MSG_SIZ];
12387
12388     if (cps->pr == NULL) return;
12389     Attention(cps);
12390     
12391     if (appData.debugMode) {
12392         TimeMark now;
12393         GetTimeMark(&now);
12394         fprintf(debugFP, "%ld >%-6s: %s", 
12395                 SubtractTimeMarks(&now, &programStartTime),
12396                 cps->which, message);
12397     }
12398     
12399     count = strlen(message);
12400     outCount = OutputToProcess(cps->pr, message, count, &error);
12401     if (outCount < count && !exiting 
12402                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12403         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12404         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12405             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12406                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12407                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12408             } else {
12409                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12410             }
12411             gameInfo.resultDetails = buf;
12412         }
12413         DisplayFatalError(buf, error, 1);
12414     }
12415 }
12416
12417 void
12418 ReceiveFromProgram(isr, closure, message, count, error)
12419      InputSourceRef isr;
12420      VOIDSTAR closure;
12421      char *message;
12422      int count;
12423      int error;
12424 {
12425     char *end_str;
12426     char buf[MSG_SIZ];
12427     ChessProgramState *cps = (ChessProgramState *)closure;
12428
12429     if (isr != cps->isr) return; /* Killed intentionally */
12430     if (count <= 0) {
12431         if (count == 0) {
12432             sprintf(buf,
12433                     _("Error: %s chess program (%s) exited unexpectedly"),
12434                     cps->which, cps->program);
12435         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12436                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12437                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12438                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12439                 } else {
12440                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12441                 }
12442                 gameInfo.resultDetails = buf;
12443             }
12444             RemoveInputSource(cps->isr);
12445             DisplayFatalError(buf, 0, 1);
12446         } else {
12447             sprintf(buf,
12448                     _("Error reading from %s chess program (%s)"),
12449                     cps->which, cps->program);
12450             RemoveInputSource(cps->isr);
12451
12452             /* [AS] Program is misbehaving badly... kill it */
12453             if( count == -2 ) {
12454                 DestroyChildProcess( cps->pr, 9 );
12455                 cps->pr = NoProc;
12456             }
12457
12458             DisplayFatalError(buf, error, 1);
12459         }
12460         return;
12461     }
12462     
12463     if ((end_str = strchr(message, '\r')) != NULL)
12464       *end_str = NULLCHAR;
12465     if ((end_str = strchr(message, '\n')) != NULL)
12466       *end_str = NULLCHAR;
12467     
12468     if (appData.debugMode) {
12469         TimeMark now; int print = 1;
12470         char *quote = ""; char c; int i;
12471
12472         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12473                 char start = message[0];
12474                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12475                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12476                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12477                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12478                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12479                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12480                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12481                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12482                         { quote = "# "; print = (appData.engineComments == 2); }
12483                 message[0] = start; // restore original message
12484         }
12485         if(print) {
12486                 GetTimeMark(&now);
12487                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12488                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12489                         quote,
12490                         message);
12491         }
12492     }
12493
12494     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12495     if (appData.icsEngineAnalyze) {
12496         if (strstr(message, "whisper") != NULL ||
12497              strstr(message, "kibitz") != NULL || 
12498             strstr(message, "tellics") != NULL) return;
12499     }
12500
12501     HandleMachineMove(message, cps);
12502 }
12503
12504
12505 void
12506 SendTimeControl(cps, mps, tc, inc, sd, st)
12507      ChessProgramState *cps;
12508      int mps, inc, sd, st;
12509      long tc;
12510 {
12511     char buf[MSG_SIZ];
12512     int seconds;
12513
12514     if( timeControl_2 > 0 ) {
12515         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12516             tc = timeControl_2;
12517         }
12518     }
12519     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12520     inc /= cps->timeOdds;
12521     st  /= cps->timeOdds;
12522
12523     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12524
12525     if (st > 0) {
12526       /* Set exact time per move, normally using st command */
12527       if (cps->stKludge) {
12528         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12529         seconds = st % 60;
12530         if (seconds == 0) {
12531           sprintf(buf, "level 1 %d\n", st/60);
12532         } else {
12533           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12534         }
12535       } else {
12536         sprintf(buf, "st %d\n", st);
12537       }
12538     } else {
12539       /* Set conventional or incremental time control, using level command */
12540       if (seconds == 0) {
12541         /* Note old gnuchess bug -- minutes:seconds used to not work.
12542            Fixed in later versions, but still avoid :seconds
12543            when seconds is 0. */
12544         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12545       } else {
12546         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12547                 seconds, inc/1000);
12548       }
12549     }
12550     SendToProgram(buf, cps);
12551
12552     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12553     /* Orthogonally, limit search to given depth */
12554     if (sd > 0) {
12555       if (cps->sdKludge) {
12556         sprintf(buf, "depth\n%d\n", sd);
12557       } else {
12558         sprintf(buf, "sd %d\n", sd);
12559       }
12560       SendToProgram(buf, cps);
12561     }
12562
12563     if(cps->nps > 0) { /* [HGM] nps */
12564         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12565         else {
12566                 sprintf(buf, "nps %d\n", cps->nps);
12567               SendToProgram(buf, cps);
12568         }
12569     }
12570 }
12571
12572 ChessProgramState *WhitePlayer()
12573 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12574 {
12575     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12576        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12577         return &second;
12578     return &first;
12579 }
12580
12581 void
12582 SendTimeRemaining(cps, machineWhite)
12583      ChessProgramState *cps;
12584      int /*boolean*/ machineWhite;
12585 {
12586     char message[MSG_SIZ];
12587     long time, otime;
12588
12589     /* Note: this routine must be called when the clocks are stopped
12590        or when they have *just* been set or switched; otherwise
12591        it will be off by the time since the current tick started.
12592     */
12593     if (machineWhite) {
12594         time = whiteTimeRemaining / 10;
12595         otime = blackTimeRemaining / 10;
12596     } else {
12597         time = blackTimeRemaining / 10;
12598         otime = whiteTimeRemaining / 10;
12599     }
12600     /* [HGM] translate opponent's time by time-odds factor */
12601     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12602     if (appData.debugMode) {
12603         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12604     }
12605
12606     if (time <= 0) time = 1;
12607     if (otime <= 0) otime = 1;
12608     
12609     sprintf(message, "time %ld\n", time);
12610     SendToProgram(message, cps);
12611
12612     sprintf(message, "otim %ld\n", otime);
12613     SendToProgram(message, cps);
12614 }
12615
12616 int
12617 BoolFeature(p, name, loc, cps)
12618      char **p;
12619      char *name;
12620      int *loc;
12621      ChessProgramState *cps;
12622 {
12623   char buf[MSG_SIZ];
12624   int len = strlen(name);
12625   int val;
12626   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12627     (*p) += len + 1;
12628     sscanf(*p, "%d", &val);
12629     *loc = (val != 0);
12630     while (**p && **p != ' ') (*p)++;
12631     sprintf(buf, "accepted %s\n", name);
12632     SendToProgram(buf, cps);
12633     return TRUE;
12634   }
12635   return FALSE;
12636 }
12637
12638 int
12639 IntFeature(p, name, loc, cps)
12640      char **p;
12641      char *name;
12642      int *loc;
12643      ChessProgramState *cps;
12644 {
12645   char buf[MSG_SIZ];
12646   int len = strlen(name);
12647   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12648     (*p) += len + 1;
12649     sscanf(*p, "%d", loc);
12650     while (**p && **p != ' ') (*p)++;
12651     sprintf(buf, "accepted %s\n", name);
12652     SendToProgram(buf, cps);
12653     return TRUE;
12654   }
12655   return FALSE;
12656 }
12657
12658 int
12659 StringFeature(p, name, loc, cps)
12660      char **p;
12661      char *name;
12662      char loc[];
12663      ChessProgramState *cps;
12664 {
12665   char buf[MSG_SIZ];
12666   int len = strlen(name);
12667   if (strncmp((*p), name, len) == 0
12668       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12669     (*p) += len + 2;
12670     sscanf(*p, "%[^\"]", loc);
12671     while (**p && **p != '\"') (*p)++;
12672     if (**p == '\"') (*p)++;
12673     sprintf(buf, "accepted %s\n", name);
12674     SendToProgram(buf, cps);
12675     return TRUE;
12676   }
12677   return FALSE;
12678 }
12679
12680 int 
12681 ParseOption(Option *opt, ChessProgramState *cps)
12682 // [HGM] options: process the string that defines an engine option, and determine
12683 // name, type, default value, and allowed value range
12684 {
12685         char *p, *q, buf[MSG_SIZ];
12686         int n, min = (-1)<<31, max = 1<<31, def;
12687
12688         if(p = strstr(opt->name, " -spin ")) {
12689             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12690             if(max < min) max = min; // enforce consistency
12691             if(def < min) def = min;
12692             if(def > max) def = max;
12693             opt->value = def;
12694             opt->min = min;
12695             opt->max = max;
12696             opt->type = Spin;
12697         } else if((p = strstr(opt->name, " -slider "))) {
12698             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12699             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12700             if(max < min) max = min; // enforce consistency
12701             if(def < min) def = min;
12702             if(def > max) def = max;
12703             opt->value = def;
12704             opt->min = min;
12705             opt->max = max;
12706             opt->type = Spin; // Slider;
12707         } else if((p = strstr(opt->name, " -string "))) {
12708             opt->textValue = p+9;
12709             opt->type = TextBox;
12710         } else if((p = strstr(opt->name, " -file "))) {
12711             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12712             opt->textValue = p+7;
12713             opt->type = TextBox; // FileName;
12714         } else if((p = strstr(opt->name, " -path "))) {
12715             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12716             opt->textValue = p+7;
12717             opt->type = TextBox; // PathName;
12718         } else if(p = strstr(opt->name, " -check ")) {
12719             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12720             opt->value = (def != 0);
12721             opt->type = CheckBox;
12722         } else if(p = strstr(opt->name, " -combo ")) {
12723             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12724             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12725             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12726             opt->value = n = 0;
12727             while(q = StrStr(q, " /// ")) {
12728                 n++; *q = 0;    // count choices, and null-terminate each of them
12729                 q += 5;
12730                 if(*q == '*') { // remember default, which is marked with * prefix
12731                     q++;
12732                     opt->value = n;
12733                 }
12734                 cps->comboList[cps->comboCnt++] = q;
12735             }
12736             cps->comboList[cps->comboCnt++] = NULL;
12737             opt->max = n + 1;
12738             opt->type = ComboBox;
12739         } else if(p = strstr(opt->name, " -button")) {
12740             opt->type = Button;
12741         } else if(p = strstr(opt->name, " -save")) {
12742             opt->type = SaveButton;
12743         } else return FALSE;
12744         *p = 0; // terminate option name
12745         // now look if the command-line options define a setting for this engine option.
12746         if(cps->optionSettings && cps->optionSettings[0])
12747             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12748         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12749                 sprintf(buf, "option %s", p);
12750                 if(p = strstr(buf, ",")) *p = 0;
12751                 strcat(buf, "\n");
12752                 SendToProgram(buf, cps);
12753         }
12754         return TRUE;
12755 }
12756
12757 void
12758 FeatureDone(cps, val)
12759      ChessProgramState* cps;
12760      int val;
12761 {
12762   DelayedEventCallback cb = GetDelayedEvent();
12763   if ((cb == InitBackEnd3 && cps == &first) ||
12764       (cb == TwoMachinesEventIfReady && cps == &second)) {
12765     CancelDelayedEvent();
12766     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12767   }
12768   cps->initDone = val;
12769 }
12770
12771 /* Parse feature command from engine */
12772 void
12773 ParseFeatures(args, cps)
12774      char* args;
12775      ChessProgramState *cps;  
12776 {
12777   char *p = args;
12778   char *q;
12779   int val;
12780   char buf[MSG_SIZ];
12781
12782   for (;;) {
12783     while (*p == ' ') p++;
12784     if (*p == NULLCHAR) return;
12785
12786     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12787     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12788     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12789     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12790     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12791     if (BoolFeature(&p, "reuse", &val, cps)) {
12792       /* Engine can disable reuse, but can't enable it if user said no */
12793       if (!val) cps->reuse = FALSE;
12794       continue;
12795     }
12796     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12797     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12798       if (gameMode == TwoMachinesPlay) {
12799         DisplayTwoMachinesTitle();
12800       } else {
12801         DisplayTitle("");
12802       }
12803       continue;
12804     }
12805     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12806     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12807     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12808     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12809     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12810     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12811     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12812     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12813     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12814     if (IntFeature(&p, "done", &val, cps)) {
12815       FeatureDone(cps, val);
12816       continue;
12817     }
12818     /* Added by Tord: */
12819     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12820     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12821     /* End of additions by Tord */
12822
12823     /* [HGM] added features: */
12824     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12825     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12826     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12827     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12828     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12829     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12830     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12831         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12832             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12833             SendToProgram(buf, cps);
12834             continue;
12835         }
12836         if(cps->nrOptions >= MAX_OPTIONS) {
12837             cps->nrOptions--;
12838             sprintf(buf, "%s engine has too many options\n", cps->which);
12839             DisplayError(buf, 0);
12840         }
12841         continue;
12842     }
12843     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12844     /* End of additions by HGM */
12845
12846     /* unknown feature: complain and skip */
12847     q = p;
12848     while (*q && *q != '=') q++;
12849     sprintf(buf, "rejected %.*s\n", q-p, p);
12850     SendToProgram(buf, cps);
12851     p = q;
12852     if (*p == '=') {
12853       p++;
12854       if (*p == '\"') {
12855         p++;
12856         while (*p && *p != '\"') p++;
12857         if (*p == '\"') p++;
12858       } else {
12859         while (*p && *p != ' ') p++;
12860       }
12861     }
12862   }
12863
12864 }
12865
12866 void
12867 PeriodicUpdatesEvent(newState)
12868      int newState;
12869 {
12870     if (newState == appData.periodicUpdates)
12871       return;
12872
12873     appData.periodicUpdates=newState;
12874
12875     /* Display type changes, so update it now */
12876 //    DisplayAnalysis();
12877
12878     /* Get the ball rolling again... */
12879     if (newState) {
12880         AnalysisPeriodicEvent(1);
12881         StartAnalysisClock();
12882     }
12883 }
12884
12885 void
12886 PonderNextMoveEvent(newState)
12887      int newState;
12888 {
12889     if (newState == appData.ponderNextMove) return;
12890     if (gameMode == EditPosition) EditPositionDone();
12891     if (newState) {
12892         SendToProgram("hard\n", &first);
12893         if (gameMode == TwoMachinesPlay) {
12894             SendToProgram("hard\n", &second);
12895         }
12896     } else {
12897         SendToProgram("easy\n", &first);
12898         thinkOutput[0] = NULLCHAR;
12899         if (gameMode == TwoMachinesPlay) {
12900             SendToProgram("easy\n", &second);
12901         }
12902     }
12903     appData.ponderNextMove = newState;
12904 }
12905
12906 void
12907 NewSettingEvent(option, command, value)
12908      char *command;
12909      int option, value;
12910 {
12911     char buf[MSG_SIZ];
12912
12913     if (gameMode == EditPosition) EditPositionDone();
12914     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12915     SendToProgram(buf, &first);
12916     if (gameMode == TwoMachinesPlay) {
12917         SendToProgram(buf, &second);
12918     }
12919 }
12920
12921 void
12922 ShowThinkingEvent()
12923 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12924 {
12925     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12926     int newState = appData.showThinking
12927         // [HGM] thinking: other features now need thinking output as well
12928         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12929     
12930     if (oldState == newState) return;
12931     oldState = newState;
12932     if (gameMode == EditPosition) EditPositionDone();
12933     if (oldState) {
12934         SendToProgram("post\n", &first);
12935         if (gameMode == TwoMachinesPlay) {
12936             SendToProgram("post\n", &second);
12937         }
12938     } else {
12939         SendToProgram("nopost\n", &first);
12940         thinkOutput[0] = NULLCHAR;
12941         if (gameMode == TwoMachinesPlay) {
12942             SendToProgram("nopost\n", &second);
12943         }
12944     }
12945 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12946 }
12947
12948 void
12949 AskQuestionEvent(title, question, replyPrefix, which)
12950      char *title; char *question; char *replyPrefix; char *which;
12951 {
12952   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12953   if (pr == NoProc) return;
12954   AskQuestion(title, question, replyPrefix, pr);
12955 }
12956
12957 void
12958 DisplayMove(moveNumber)
12959      int moveNumber;
12960 {
12961     char message[MSG_SIZ];
12962     char res[MSG_SIZ];
12963     char cpThinkOutput[MSG_SIZ];
12964
12965     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12966     
12967     if (moveNumber == forwardMostMove - 1 || 
12968         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12969
12970         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12971
12972         if (strchr(cpThinkOutput, '\n')) {
12973             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12974         }
12975     } else {
12976         *cpThinkOutput = NULLCHAR;
12977     }
12978
12979     /* [AS] Hide thinking from human user */
12980     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12981         *cpThinkOutput = NULLCHAR;
12982         if( thinkOutput[0] != NULLCHAR ) {
12983             int i;
12984
12985             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12986                 cpThinkOutput[i] = '.';
12987             }
12988             cpThinkOutput[i] = NULLCHAR;
12989             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12990         }
12991     }
12992
12993     if (moveNumber == forwardMostMove - 1 &&
12994         gameInfo.resultDetails != NULL) {
12995         if (gameInfo.resultDetails[0] == NULLCHAR) {
12996             sprintf(res, " %s", PGNResult(gameInfo.result));
12997         } else {
12998             sprintf(res, " {%s} %s",
12999                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13000         }
13001     } else {
13002         res[0] = NULLCHAR;
13003     }
13004
13005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13006         DisplayMessage(res, cpThinkOutput);
13007     } else {
13008         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13009                 WhiteOnMove(moveNumber) ? " " : ".. ",
13010                 parseList[moveNumber], res);
13011         DisplayMessage(message, cpThinkOutput);
13012     }
13013 }
13014
13015 void
13016 DisplayComment(moveNumber, text)
13017      int moveNumber;
13018      char *text;
13019 {
13020     char title[MSG_SIZ];
13021     char buf[8000]; // comment can be long!
13022     int score, depth;
13023
13024     if( appData.autoDisplayComment ) {
13025         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13026             strcpy(title, "Comment");
13027         } else {
13028             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13029                     WhiteOnMove(moveNumber) ? " " : ".. ",
13030                     parseList[moveNumber]);
13031         }
13032         // [HGM] PV info: display PV info together with (or as) comment
13033         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13034             if(text == NULL) text = "";                                           
13035             score = pvInfoList[moveNumber].score;
13036             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13037                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13038             text = buf;
13039         }
13040     } else title[0] = 0;
13041
13042     if (text != NULL)
13043         CommentPopUp(title, text);
13044 }
13045
13046 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13047  * might be busy thinking or pondering.  It can be omitted if your
13048  * gnuchess is configured to stop thinking immediately on any user
13049  * input.  However, that gnuchess feature depends on the FIONREAD
13050  * ioctl, which does not work properly on some flavors of Unix.
13051  */
13052 void
13053 Attention(cps)
13054      ChessProgramState *cps;
13055 {
13056 #if ATTENTION
13057     if (!cps->useSigint) return;
13058     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13059     switch (gameMode) {
13060       case MachinePlaysWhite:
13061       case MachinePlaysBlack:
13062       case TwoMachinesPlay:
13063       case IcsPlayingWhite:
13064       case IcsPlayingBlack:
13065       case AnalyzeMode:
13066       case AnalyzeFile:
13067         /* Skip if we know it isn't thinking */
13068         if (!cps->maybeThinking) return;
13069         if (appData.debugMode)
13070           fprintf(debugFP, "Interrupting %s\n", cps->which);
13071         InterruptChildProcess(cps->pr);
13072         cps->maybeThinking = FALSE;
13073         break;
13074       default:
13075         break;
13076     }
13077 #endif /*ATTENTION*/
13078 }
13079
13080 int
13081 CheckFlags()
13082 {
13083     if (whiteTimeRemaining <= 0) {
13084         if (!whiteFlag) {
13085             whiteFlag = TRUE;
13086             if (appData.icsActive) {
13087                 if (appData.autoCallFlag &&
13088                     gameMode == IcsPlayingBlack && !blackFlag) {
13089                   SendToICS(ics_prefix);
13090                   SendToICS("flag\n");
13091                 }
13092             } else {
13093                 if (blackFlag) {
13094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13095                 } else {
13096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13097                     if (appData.autoCallFlag) {
13098                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13099                         return TRUE;
13100                     }
13101                 }
13102             }
13103         }
13104     }
13105     if (blackTimeRemaining <= 0) {
13106         if (!blackFlag) {
13107             blackFlag = TRUE;
13108             if (appData.icsActive) {
13109                 if (appData.autoCallFlag &&
13110                     gameMode == IcsPlayingWhite && !whiteFlag) {
13111                   SendToICS(ics_prefix);
13112                   SendToICS("flag\n");
13113                 }
13114             } else {
13115                 if (whiteFlag) {
13116                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13117                 } else {
13118                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13119                     if (appData.autoCallFlag) {
13120                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13121                         return TRUE;
13122                     }
13123                 }
13124             }
13125         }
13126     }
13127     return FALSE;
13128 }
13129
13130 void
13131 CheckTimeControl()
13132 {
13133     if (!appData.clockMode || appData.icsActive ||
13134         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13135
13136     /*
13137      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13138      */
13139     if ( !WhiteOnMove(forwardMostMove) )
13140         /* White made time control */
13141         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13142         /* [HGM] time odds: correct new time quota for time odds! */
13143                                             / WhitePlayer()->timeOdds;
13144       else
13145         /* Black made time control */
13146         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13147                                             / WhitePlayer()->other->timeOdds;
13148 }
13149
13150 void
13151 DisplayBothClocks()
13152 {
13153     int wom = gameMode == EditPosition ?
13154       !blackPlaysFirst : WhiteOnMove(currentMove);
13155     DisplayWhiteClock(whiteTimeRemaining, wom);
13156     DisplayBlackClock(blackTimeRemaining, !wom);
13157 }
13158
13159
13160 /* Timekeeping seems to be a portability nightmare.  I think everyone
13161    has ftime(), but I'm really not sure, so I'm including some ifdefs
13162    to use other calls if you don't.  Clocks will be less accurate if
13163    you have neither ftime nor gettimeofday.
13164 */
13165
13166 /* VS 2008 requires the #include outside of the function */
13167 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13168 #include <sys/timeb.h>
13169 #endif
13170
13171 /* Get the current time as a TimeMark */
13172 void
13173 GetTimeMark(tm)
13174      TimeMark *tm;
13175 {
13176 #if HAVE_GETTIMEOFDAY
13177
13178     struct timeval timeVal;
13179     struct timezone timeZone;
13180
13181     gettimeofday(&timeVal, &timeZone);
13182     tm->sec = (long) timeVal.tv_sec; 
13183     tm->ms = (int) (timeVal.tv_usec / 1000L);
13184
13185 #else /*!HAVE_GETTIMEOFDAY*/
13186 #if HAVE_FTIME
13187
13188 // include <sys/timeb.h> / moved to just above start of function
13189     struct timeb timeB;
13190
13191     ftime(&timeB);
13192     tm->sec = (long) timeB.time;
13193     tm->ms = (int) timeB.millitm;
13194
13195 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13196     tm->sec = (long) time(NULL);
13197     tm->ms = 0;
13198 #endif
13199 #endif
13200 }
13201
13202 /* Return the difference in milliseconds between two
13203    time marks.  We assume the difference will fit in a long!
13204 */
13205 long
13206 SubtractTimeMarks(tm2, tm1)
13207      TimeMark *tm2, *tm1;
13208 {
13209     return 1000L*(tm2->sec - tm1->sec) +
13210            (long) (tm2->ms - tm1->ms);
13211 }
13212
13213
13214 /*
13215  * Code to manage the game clocks.
13216  *
13217  * In tournament play, black starts the clock and then white makes a move.
13218  * We give the human user a slight advantage if he is playing white---the
13219  * clocks don't run until he makes his first move, so it takes zero time.
13220  * Also, we don't account for network lag, so we could get out of sync
13221  * with GNU Chess's clock -- but then, referees are always right.  
13222  */
13223
13224 static TimeMark tickStartTM;
13225 static long intendedTickLength;
13226
13227 long
13228 NextTickLength(timeRemaining)
13229      long timeRemaining;
13230 {
13231     long nominalTickLength, nextTickLength;
13232
13233     if (timeRemaining > 0L && timeRemaining <= 10000L)
13234       nominalTickLength = 100L;
13235     else
13236       nominalTickLength = 1000L;
13237     nextTickLength = timeRemaining % nominalTickLength;
13238     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13239
13240     return nextTickLength;
13241 }
13242
13243 /* Adjust clock one minute up or down */
13244 void
13245 AdjustClock(Boolean which, int dir)
13246 {
13247     if(which) blackTimeRemaining += 60000*dir;
13248     else      whiteTimeRemaining += 60000*dir;
13249     DisplayBothClocks();
13250 }
13251
13252 /* Stop clocks and reset to a fresh time control */
13253 void
13254 ResetClocks() 
13255 {
13256     (void) StopClockTimer();
13257     if (appData.icsActive) {
13258         whiteTimeRemaining = blackTimeRemaining = 0;
13259     } else { /* [HGM] correct new time quote for time odds */
13260         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13261         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13262     }
13263     if (whiteFlag || blackFlag) {
13264         DisplayTitle("");
13265         whiteFlag = blackFlag = FALSE;
13266     }
13267     DisplayBothClocks();
13268 }
13269
13270 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13271
13272 /* Decrement running clock by amount of time that has passed */
13273 void
13274 DecrementClocks()
13275 {
13276     long timeRemaining;
13277     long lastTickLength, fudge;
13278     TimeMark now;
13279
13280     if (!appData.clockMode) return;
13281     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13282         
13283     GetTimeMark(&now);
13284
13285     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13286
13287     /* Fudge if we woke up a little too soon */
13288     fudge = intendedTickLength - lastTickLength;
13289     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13290
13291     if (WhiteOnMove(forwardMostMove)) {
13292         if(whiteNPS >= 0) lastTickLength = 0;
13293         timeRemaining = whiteTimeRemaining -= lastTickLength;
13294         DisplayWhiteClock(whiteTimeRemaining - fudge,
13295                           WhiteOnMove(currentMove));
13296     } else {
13297         if(blackNPS >= 0) lastTickLength = 0;
13298         timeRemaining = blackTimeRemaining -= lastTickLength;
13299         DisplayBlackClock(blackTimeRemaining - fudge,
13300                           !WhiteOnMove(currentMove));
13301     }
13302
13303     if (CheckFlags()) return;
13304         
13305     tickStartTM = now;
13306     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13307     StartClockTimer(intendedTickLength);
13308
13309     /* if the time remaining has fallen below the alarm threshold, sound the
13310      * alarm. if the alarm has sounded and (due to a takeback or time control
13311      * with increment) the time remaining has increased to a level above the
13312      * threshold, reset the alarm so it can sound again. 
13313      */
13314     
13315     if (appData.icsActive && appData.icsAlarm) {
13316
13317         /* make sure we are dealing with the user's clock */
13318         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13319                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13320            )) return;
13321
13322         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13323             alarmSounded = FALSE;
13324         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13325             PlayAlarmSound();
13326             alarmSounded = TRUE;
13327         }
13328     }
13329 }
13330
13331
13332 /* A player has just moved, so stop the previously running
13333    clock and (if in clock mode) start the other one.
13334    We redisplay both clocks in case we're in ICS mode, because
13335    ICS gives us an update to both clocks after every move.
13336    Note that this routine is called *after* forwardMostMove
13337    is updated, so the last fractional tick must be subtracted
13338    from the color that is *not* on move now.
13339 */
13340 void
13341 SwitchClocks()
13342 {
13343     long lastTickLength;
13344     TimeMark now;
13345     int flagged = FALSE;
13346
13347     GetTimeMark(&now);
13348
13349     if (StopClockTimer() && appData.clockMode) {
13350         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13351         if (WhiteOnMove(forwardMostMove)) {
13352             if(blackNPS >= 0) lastTickLength = 0;
13353             blackTimeRemaining -= lastTickLength;
13354            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13355 //         if(pvInfoList[forwardMostMove-1].time == -1)
13356                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13357                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13358         } else {
13359            if(whiteNPS >= 0) lastTickLength = 0;
13360            whiteTimeRemaining -= lastTickLength;
13361            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13362 //         if(pvInfoList[forwardMostMove-1].time == -1)
13363                  pvInfoList[forwardMostMove-1].time = 
13364                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13365         }
13366         flagged = CheckFlags();
13367     }
13368     CheckTimeControl();
13369
13370     if (flagged || !appData.clockMode) return;
13371
13372     switch (gameMode) {
13373       case MachinePlaysBlack:
13374       case MachinePlaysWhite:
13375       case BeginningOfGame:
13376         if (pausing) return;
13377         break;
13378
13379       case EditGame:
13380       case PlayFromGameFile:
13381       case IcsExamining:
13382         return;
13383
13384       default:
13385         break;
13386     }
13387
13388     tickStartTM = now;
13389     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13390       whiteTimeRemaining : blackTimeRemaining);
13391     StartClockTimer(intendedTickLength);
13392 }
13393         
13394
13395 /* Stop both clocks */
13396 void
13397 StopClocks()
13398 {       
13399     long lastTickLength;
13400     TimeMark now;
13401
13402     if (!StopClockTimer()) return;
13403     if (!appData.clockMode) return;
13404
13405     GetTimeMark(&now);
13406
13407     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13408     if (WhiteOnMove(forwardMostMove)) {
13409         if(whiteNPS >= 0) lastTickLength = 0;
13410         whiteTimeRemaining -= lastTickLength;
13411         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13412     } else {
13413         if(blackNPS >= 0) lastTickLength = 0;
13414         blackTimeRemaining -= lastTickLength;
13415         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13416     }
13417     CheckFlags();
13418 }
13419         
13420 /* Start clock of player on move.  Time may have been reset, so
13421    if clock is already running, stop and restart it. */
13422 void
13423 StartClocks()
13424 {
13425     (void) StopClockTimer(); /* in case it was running already */
13426     DisplayBothClocks();
13427     if (CheckFlags()) return;
13428
13429     if (!appData.clockMode) return;
13430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13431
13432     GetTimeMark(&tickStartTM);
13433     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13434       whiteTimeRemaining : blackTimeRemaining);
13435
13436    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13437     whiteNPS = blackNPS = -1; 
13438     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13439        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13440         whiteNPS = first.nps;
13441     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13442        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13443         blackNPS = first.nps;
13444     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13445         whiteNPS = second.nps;
13446     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13447         blackNPS = second.nps;
13448     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13449
13450     StartClockTimer(intendedTickLength);
13451 }
13452
13453 char *
13454 TimeString(ms)
13455      long ms;
13456 {
13457     long second, minute, hour, day;
13458     char *sign = "";
13459     static char buf[32];
13460     
13461     if (ms > 0 && ms <= 9900) {
13462       /* convert milliseconds to tenths, rounding up */
13463       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13464
13465       sprintf(buf, " %03.1f ", tenths/10.0);
13466       return buf;
13467     }
13468
13469     /* convert milliseconds to seconds, rounding up */
13470     /* use floating point to avoid strangeness of integer division
13471        with negative dividends on many machines */
13472     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13473
13474     if (second < 0) {
13475         sign = "-";
13476         second = -second;
13477     }
13478     
13479     day = second / (60 * 60 * 24);
13480     second = second % (60 * 60 * 24);
13481     hour = second / (60 * 60);
13482     second = second % (60 * 60);
13483     minute = second / 60;
13484     second = second % 60;
13485     
13486     if (day > 0)
13487       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13488               sign, day, hour, minute, second);
13489     else if (hour > 0)
13490       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13491     else
13492       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13493     
13494     return buf;
13495 }
13496
13497
13498 /*
13499  * This is necessary because some C libraries aren't ANSI C compliant yet.
13500  */
13501 char *
13502 StrStr(string, match)
13503      char *string, *match;
13504 {
13505     int i, length;
13506     
13507     length = strlen(match);
13508     
13509     for (i = strlen(string) - length; i >= 0; i--, string++)
13510       if (!strncmp(match, string, length))
13511         return string;
13512     
13513     return NULL;
13514 }
13515
13516 char *
13517 StrCaseStr(string, match)
13518      char *string, *match;
13519 {
13520     int i, j, length;
13521     
13522     length = strlen(match);
13523     
13524     for (i = strlen(string) - length; i >= 0; i--, string++) {
13525         for (j = 0; j < length; j++) {
13526             if (ToLower(match[j]) != ToLower(string[j]))
13527               break;
13528         }
13529         if (j == length) return string;
13530     }
13531
13532     return NULL;
13533 }
13534
13535 #ifndef _amigados
13536 int
13537 StrCaseCmp(s1, s2)
13538      char *s1, *s2;
13539 {
13540     char c1, c2;
13541     
13542     for (;;) {
13543         c1 = ToLower(*s1++);
13544         c2 = ToLower(*s2++);
13545         if (c1 > c2) return 1;
13546         if (c1 < c2) return -1;
13547         if (c1 == NULLCHAR) return 0;
13548     }
13549 }
13550
13551
13552 int
13553 ToLower(c)
13554      int c;
13555 {
13556     return isupper(c) ? tolower(c) : c;
13557 }
13558
13559
13560 int
13561 ToUpper(c)
13562      int c;
13563 {
13564     return islower(c) ? toupper(c) : c;
13565 }
13566 #endif /* !_amigados    */
13567
13568 char *
13569 StrSave(s)
13570      char *s;
13571 {
13572     char *ret;
13573
13574     if ((ret = (char *) malloc(strlen(s) + 1))) {
13575         strcpy(ret, s);
13576     }
13577     return ret;
13578 }
13579
13580 char *
13581 StrSavePtr(s, savePtr)
13582      char *s, **savePtr;
13583 {
13584     if (*savePtr) {
13585         free(*savePtr);
13586     }
13587     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13588         strcpy(*savePtr, s);
13589     }
13590     return(*savePtr);
13591 }
13592
13593 char *
13594 PGNDate()
13595 {
13596     time_t clock;
13597     struct tm *tm;
13598     char buf[MSG_SIZ];
13599
13600     clock = time((time_t *)NULL);
13601     tm = localtime(&clock);
13602     sprintf(buf, "%04d.%02d.%02d",
13603             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13604     return StrSave(buf);
13605 }
13606
13607
13608 char *
13609 PositionToFEN(move, overrideCastling)
13610      int move;
13611      char *overrideCastling;
13612 {
13613     int i, j, fromX, fromY, toX, toY;
13614     int whiteToPlay;
13615     char buf[128];
13616     char *p, *q;
13617     int emptycount;
13618     ChessSquare piece;
13619
13620     whiteToPlay = (gameMode == EditPosition) ?
13621       !blackPlaysFirst : (move % 2 == 0);
13622     p = buf;
13623
13624     /* Piece placement data */
13625     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13626         emptycount = 0;
13627         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13628             if (boards[move][i][j] == EmptySquare) {
13629                 emptycount++;
13630             } else { ChessSquare piece = boards[move][i][j];
13631                 if (emptycount > 0) {
13632                     if(emptycount<10) /* [HGM] can be >= 10 */
13633                         *p++ = '0' + emptycount;
13634                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13635                     emptycount = 0;
13636                 }
13637                 if(PieceToChar(piece) == '+') {
13638                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13639                     *p++ = '+';
13640                     piece = (ChessSquare)(DEMOTED piece);
13641                 } 
13642                 *p++ = PieceToChar(piece);
13643                 if(p[-1] == '~') {
13644                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13645                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13646                     *p++ = '~';
13647                 }
13648             }
13649         }
13650         if (emptycount > 0) {
13651             if(emptycount<10) /* [HGM] can be >= 10 */
13652                 *p++ = '0' + emptycount;
13653             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13654             emptycount = 0;
13655         }
13656         *p++ = '/';
13657     }
13658     *(p - 1) = ' ';
13659
13660     /* [HGM] print Crazyhouse or Shogi holdings */
13661     if( gameInfo.holdingsWidth ) {
13662         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13663         q = p;
13664         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13665             piece = boards[move][i][BOARD_WIDTH-1];
13666             if( piece != EmptySquare )
13667               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13668                   *p++ = PieceToChar(piece);
13669         }
13670         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13671             piece = boards[move][BOARD_HEIGHT-i-1][0];
13672             if( piece != EmptySquare )
13673               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13674                   *p++ = PieceToChar(piece);
13675         }
13676
13677         if( q == p ) *p++ = '-';
13678         *p++ = ']';
13679         *p++ = ' ';
13680     }
13681
13682     /* Active color */
13683     *p++ = whiteToPlay ? 'w' : 'b';
13684     *p++ = ' ';
13685
13686   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13687     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13688   } else {
13689   if(nrCastlingRights) {
13690      q = p;
13691      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13692        /* [HGM] write directly from rights */
13693            if(castlingRights[move][2] >= 0 &&
13694               castlingRights[move][0] >= 0   )
13695                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13696            if(castlingRights[move][2] >= 0 &&
13697               castlingRights[move][1] >= 0   )
13698                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13699            if(castlingRights[move][5] >= 0 &&
13700               castlingRights[move][3] >= 0   )
13701                 *p++ = castlingRights[move][3] + AAA;
13702            if(castlingRights[move][5] >= 0 &&
13703               castlingRights[move][4] >= 0   )
13704                 *p++ = castlingRights[move][4] + AAA;
13705      } else {
13706
13707         /* [HGM] write true castling rights */
13708         if( nrCastlingRights == 6 ) {
13709             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13710                castlingRights[move][2] >= 0  ) *p++ = 'K';
13711             if(castlingRights[move][1] == BOARD_LEFT &&
13712                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13713             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13714                castlingRights[move][5] >= 0  ) *p++ = 'k';
13715             if(castlingRights[move][4] == BOARD_LEFT &&
13716                castlingRights[move][5] >= 0  ) *p++ = 'q';
13717         }
13718      }
13719      if (q == p) *p++ = '-'; /* No castling rights */
13720      *p++ = ' ';
13721   }
13722
13723   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13724      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13725     /* En passant target square */
13726     if (move > backwardMostMove) {
13727         fromX = moveList[move - 1][0] - AAA;
13728         fromY = moveList[move - 1][1] - ONE;
13729         toX = moveList[move - 1][2] - AAA;
13730         toY = moveList[move - 1][3] - ONE;
13731         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13732             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13733             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13734             fromX == toX) {
13735             /* 2-square pawn move just happened */
13736             *p++ = toX + AAA;
13737             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13738         } else {
13739             *p++ = '-';
13740         }
13741     } else if(move == backwardMostMove) {
13742         // [HGM] perhaps we should always do it like this, and forget the above?
13743         if(epStatus[move] >= 0) {
13744             *p++ = epStatus[move] + AAA;
13745             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13746         } else {
13747             *p++ = '-';
13748         }
13749     } else {
13750         *p++ = '-';
13751     }
13752     *p++ = ' ';
13753   }
13754   }
13755
13756     /* [HGM] find reversible plies */
13757     {   int i = 0, j=move;
13758
13759         if (appData.debugMode) { int k;
13760             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13761             for(k=backwardMostMove; k<=forwardMostMove; k++)
13762                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13763
13764         }
13765
13766         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13767         if( j == backwardMostMove ) i += initialRulePlies;
13768         sprintf(p, "%d ", i);
13769         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13770     }
13771     /* Fullmove number */
13772     sprintf(p, "%d", (move / 2) + 1);
13773     
13774     return StrSave(buf);
13775 }
13776
13777 Boolean
13778 ParseFEN(board, blackPlaysFirst, fen)
13779     Board board;
13780      int *blackPlaysFirst;
13781      char *fen;
13782 {
13783     int i, j;
13784     char *p;
13785     int emptycount;
13786     ChessSquare piece;
13787
13788     p = fen;
13789
13790     /* [HGM] by default clear Crazyhouse holdings, if present */
13791     if(gameInfo.holdingsWidth) {
13792        for(i=0; i<BOARD_HEIGHT; i++) {
13793            board[i][0]             = EmptySquare; /* black holdings */
13794            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13795            board[i][1]             = (ChessSquare) 0; /* black counts */
13796            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13797        }
13798     }
13799
13800     /* Piece placement data */
13801     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13802         j = 0;
13803         for (;;) {
13804             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13805                 if (*p == '/') p++;
13806                 emptycount = gameInfo.boardWidth - j;
13807                 while (emptycount--)
13808                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13809                 break;
13810 #if(BOARD_SIZE >= 10)
13811             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13812                 p++; emptycount=10;
13813                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13814                 while (emptycount--)
13815                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13816 #endif
13817             } else if (isdigit(*p)) {
13818                 emptycount = *p++ - '0';
13819                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13820                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13821                 while (emptycount--)
13822                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13823             } else if (*p == '+' || isalpha(*p)) {
13824                 if (j >= gameInfo.boardWidth) return FALSE;
13825                 if(*p=='+') {
13826                     piece = CharToPiece(*++p);
13827                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13828                     piece = (ChessSquare) (PROMOTED piece ); p++;
13829                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13830                 } else piece = CharToPiece(*p++);
13831
13832                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13833                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13834                     piece = (ChessSquare) (PROMOTED piece);
13835                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13836                     p++;
13837                 }
13838                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13839             } else {
13840                 return FALSE;
13841             }
13842         }
13843     }
13844     while (*p == '/' || *p == ' ') p++;
13845
13846     /* [HGM] look for Crazyhouse holdings here */
13847     while(*p==' ') p++;
13848     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13849         if(*p == '[') p++;
13850         if(*p == '-' ) *p++; /* empty holdings */ else {
13851             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13852             /* if we would allow FEN reading to set board size, we would   */
13853             /* have to add holdings and shift the board read so far here   */
13854             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13855                 *p++;
13856                 if((int) piece >= (int) BlackPawn ) {
13857                     i = (int)piece - (int)BlackPawn;
13858                     i = PieceToNumber((ChessSquare)i);
13859                     if( i >= gameInfo.holdingsSize ) return FALSE;
13860                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13861                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13862                 } else {
13863                     i = (int)piece - (int)WhitePawn;
13864                     i = PieceToNumber((ChessSquare)i);
13865                     if( i >= gameInfo.holdingsSize ) return FALSE;
13866                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13867                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13868                 }
13869             }
13870         }
13871         if(*p == ']') *p++;
13872     }
13873
13874     while(*p == ' ') p++;
13875
13876     /* Active color */
13877     switch (*p++) {
13878       case 'w':
13879         *blackPlaysFirst = FALSE;
13880         break;
13881       case 'b': 
13882         *blackPlaysFirst = TRUE;
13883         break;
13884       default:
13885         return FALSE;
13886     }
13887
13888     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13889     /* return the extra info in global variiables             */
13890
13891     /* set defaults in case FEN is incomplete */
13892     FENepStatus = EP_UNKNOWN;
13893     for(i=0; i<nrCastlingRights; i++ ) {
13894         FENcastlingRights[i] =
13895             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13896     }   /* assume possible unless obviously impossible */
13897     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13898     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13899     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13900     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13901     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13902     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13903     FENrulePlies = 0;
13904
13905     while(*p==' ') p++;
13906     if(nrCastlingRights) {
13907       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13908           /* castling indicator present, so default becomes no castlings */
13909           for(i=0; i<nrCastlingRights; i++ ) {
13910                  FENcastlingRights[i] = -1;
13911           }
13912       }
13913       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13914              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13915              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13916              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13917         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13918
13919         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13920             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13921             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13922         }
13923         switch(c) {
13924           case'K':
13925               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13926               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13927               FENcastlingRights[2] = whiteKingFile;
13928               break;
13929           case'Q':
13930               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13931               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13932               FENcastlingRights[2] = whiteKingFile;
13933               break;
13934           case'k':
13935               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13936               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13937               FENcastlingRights[5] = blackKingFile;
13938               break;
13939           case'q':
13940               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13941               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13942               FENcastlingRights[5] = blackKingFile;
13943           case '-':
13944               break;
13945           default: /* FRC castlings */
13946               if(c >= 'a') { /* black rights */
13947                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13948                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13949                   if(i == BOARD_RGHT) break;
13950                   FENcastlingRights[5] = i;
13951                   c -= AAA;
13952                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13953                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13954                   if(c > i)
13955                       FENcastlingRights[3] = c;
13956                   else
13957                       FENcastlingRights[4] = c;
13958               } else { /* white rights */
13959                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13960                     if(board[0][i] == WhiteKing) break;
13961                   if(i == BOARD_RGHT) break;
13962                   FENcastlingRights[2] = i;
13963                   c -= AAA - 'a' + 'A';
13964                   if(board[0][c] >= WhiteKing) break;
13965                   if(c > i)
13966                       FENcastlingRights[0] = c;
13967                   else
13968                       FENcastlingRights[1] = c;
13969               }
13970         }
13971       }
13972     if (appData.debugMode) {
13973         fprintf(debugFP, "FEN castling rights:");
13974         for(i=0; i<nrCastlingRights; i++)
13975         fprintf(debugFP, " %d", FENcastlingRights[i]);
13976         fprintf(debugFP, "\n");
13977     }
13978
13979       while(*p==' ') p++;
13980     }
13981
13982     /* read e.p. field in games that know e.p. capture */
13983     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13984        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13985       if(*p=='-') {
13986         p++; FENepStatus = EP_NONE;
13987       } else {
13988          char c = *p++ - AAA;
13989
13990          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13991          if(*p >= '0' && *p <='9') *p++;
13992          FENepStatus = c;
13993       }
13994     }
13995
13996
13997     if(sscanf(p, "%d", &i) == 1) {
13998         FENrulePlies = i; /* 50-move ply counter */
13999         /* (The move number is still ignored)    */
14000     }
14001
14002     return TRUE;
14003 }
14004       
14005 void
14006 EditPositionPasteFEN(char *fen)
14007 {
14008   if (fen != NULL) {
14009     Board initial_position;
14010
14011     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14012       DisplayError(_("Bad FEN position in clipboard"), 0);
14013       return ;
14014     } else {
14015       int savedBlackPlaysFirst = blackPlaysFirst;
14016       EditPositionEvent();
14017       blackPlaysFirst = savedBlackPlaysFirst;
14018       CopyBoard(boards[0], initial_position);
14019           /* [HGM] copy FEN attributes as well */
14020           {   int i;
14021               initialRulePlies = FENrulePlies;
14022               epStatus[0] = FENepStatus;
14023               for( i=0; i<nrCastlingRights; i++ )
14024                   castlingRights[0][i] = FENcastlingRights[i];
14025           }
14026       EditPositionDone();
14027       DisplayBothClocks();
14028       DrawPosition(FALSE, boards[currentMove]);
14029     }
14030   }
14031 }
14032
14033 static char cseq[12] = "\\   ";
14034
14035 Boolean set_cont_sequence(char *new_seq)
14036 {
14037     int len;
14038     Boolean ret;
14039
14040     // handle bad attempts to set the sequence
14041         if (!new_seq)
14042                 return 0; // acceptable error - no debug
14043
14044     len = strlen(new_seq);
14045     ret = (len > 0) && (len < sizeof(cseq));
14046     if (ret)
14047         strcpy(cseq, new_seq);
14048     else if (appData.debugMode)
14049         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14050     return ret;
14051 }
14052
14053 /*
14054     reformat a source message so words don't cross the width boundary.  internal
14055     newlines are not removed.  returns the wrapped size (no null character unless
14056     included in source message).  If dest is NULL, only calculate the size required
14057     for the dest buffer.  lp argument indicats line position upon entry, and it's
14058     passed back upon exit.
14059 */
14060 int wrap(char *dest, char *src, int count, int width, int *lp)
14061 {
14062     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14063
14064     cseq_len = strlen(cseq);
14065     old_line = line = *lp;
14066     ansi = len = clen = 0;
14067
14068     for (i=0; i < count; i++)
14069     {
14070         if (src[i] == '\033')
14071             ansi = 1;
14072
14073         // if we hit the width, back up
14074         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14075         {
14076             // store i & len in case the word is too long
14077             old_i = i, old_len = len;
14078
14079             // find the end of the last word
14080             while (i && src[i] != ' ' && src[i] != '\n')
14081             {
14082                 i--;
14083                 len--;
14084             }
14085
14086             // word too long?  restore i & len before splitting it
14087             if ((old_i-i+clen) >= width)
14088             {
14089                 i = old_i;
14090                 len = old_len;
14091             }
14092
14093             // extra space?
14094             if (i && src[i-1] == ' ')
14095                 len--;
14096
14097             if (src[i] != ' ' && src[i] != '\n')
14098             {
14099                 i--;
14100                 if (len)
14101                     len--;
14102             }
14103
14104             // now append the newline and continuation sequence
14105             if (dest)
14106                 dest[len] = '\n';
14107             len++;
14108             if (dest)
14109                 strncpy(dest+len, cseq, cseq_len);
14110             len += cseq_len;
14111             line = cseq_len;
14112             clen = cseq_len;
14113             continue;
14114         }
14115
14116         if (dest)
14117             dest[len] = src[i];
14118         len++;
14119         if (!ansi)
14120             line++;
14121         if (src[i] == '\n')
14122             line = 0;
14123         if (src[i] == 'm')
14124             ansi = 0;
14125     }
14126     if (dest && appData.debugMode)
14127     {
14128         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14129             count, width, line, len, *lp);
14130         show_bytes(debugFP, src, count);
14131         fprintf(debugFP, "\ndest: ");
14132         show_bytes(debugFP, dest, len);
14133         fprintf(debugFP, "\n");
14134     }
14135     *lp = dest ? line : old_line;
14136
14137     return len;
14138 }