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