fix for new way of saving castling and e.p. information
[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));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0; 
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 ChessSquare  FIDEArray[2][BOARD_FILES] = {
454     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
455         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
456     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
457         BlackKing, BlackBishop, BlackKnight, BlackRook }
458 };
459
460 ChessSquare twoKingsArray[2][BOARD_FILES] = {
461     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
462         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
463     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
464         BlackKing, BlackKing, BlackKnight, BlackRook }
465 };
466
467 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
469         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
470     { BlackRook, BlackMan, BlackBishop, BlackQueen,
471         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
472 };
473
474 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
475     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
482     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
483         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
485         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
486 };
487
488
489 #if (BOARD_FILES>=10)
490 ChessSquare ShogiArray[2][BOARD_FILES] = {
491     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
492         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
493     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
494         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
495 };
496
497 ChessSquare XiangqiArray[2][BOARD_FILES] = {
498     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
499         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
500     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
501         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
502 };
503
504 ChessSquare CapablancaArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
506         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
508         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
509 };
510
511 ChessSquare GreatArray[2][BOARD_FILES] = {
512     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
513         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
514     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
515         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
516 };
517
518 ChessSquare JanusArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
520         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
521     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
522         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
523 };
524
525 #ifdef GOTHIC
526 ChessSquare GothicArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
528         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
530         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
531 };
532 #else // !GOTHIC
533 #define GothicArray CapablancaArray
534 #endif // !GOTHIC
535
536 #ifdef FALCON
537 ChessSquare FalconArray[2][BOARD_FILES] = {
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
539         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
541         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
542 };
543 #else // !FALCON
544 #define FalconArray CapablancaArray
545 #endif // !FALCON
546
547 #else // !(BOARD_FILES>=10)
548 #define XiangqiPosition FIDEArray
549 #define CapablancaArray FIDEArray
550 #define GothicArray FIDEArray
551 #define GreatArray FIDEArray
552 #endif // !(BOARD_FILES>=10)
553
554 #if (BOARD_FILES>=12)
555 ChessSquare CourierArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
559         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
560 };
561 #else // !(BOARD_FILES>=12)
562 #define CourierArray CapablancaArray
563 #endif // !(BOARD_FILES>=12)
564
565
566 Board initialPosition;
567
568
569 /* Convert str to a rating. Checks for special cases of "----",
570
571    "++++", etc. Also strips ()'s */
572 int
573 string_to_rating(str)
574   char *str;
575 {
576   while(*str && !isdigit(*str)) ++str;
577   if (!*str)
578     return 0;   /* One of the special "no rating" cases */
579   else
580     return atoi(str);
581 }
582
583 void
584 ClearProgramStats()
585 {
586     /* Init programStats */
587     programStats.movelist[0] = 0;
588     programStats.depth = 0;
589     programStats.nr_moves = 0;
590     programStats.moves_left = 0;
591     programStats.nodes = 0;
592     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
593     programStats.score = 0;
594     programStats.got_only_move = 0;
595     programStats.got_fail = 0;
596     programStats.line_is_book = 0;
597 }
598
599 void
600 InitBackEnd1()
601 {
602     int matched, min, sec;
603
604     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
605
606     GetTimeMark(&programStartTime);
607     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
608
609     ClearProgramStats();
610     programStats.ok_to_send = 1;
611     programStats.seen_stat = 0;
612
613     /*
614      * Initialize game list
615      */
616     ListNew(&gameList);
617
618
619     /*
620      * Internet chess server status
621      */
622     if (appData.icsActive) {
623         appData.matchMode = FALSE;
624         appData.matchGames = 0;
625 #if ZIPPY       
626         appData.noChessProgram = !appData.zippyPlay;
627 #else
628         appData.zippyPlay = FALSE;
629         appData.zippyTalk = FALSE;
630         appData.noChessProgram = TRUE;
631 #endif
632         if (*appData.icsHelper != NULLCHAR) {
633             appData.useTelnet = TRUE;
634             appData.telnetProgram = appData.icsHelper;
635         }
636     } else {
637         appData.zippyTalk = appData.zippyPlay = FALSE;
638     }
639
640     /* [AS] Initialize pv info list [HGM] and game state */
641     {
642         int i, j;
643
644         for( i=0; i<MAX_MOVES; i++ ) {
645             pvInfoList[i].depth = -1;
646             boards[i][EP_STATUS] = EP_NONE;
647             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
648         }
649     }
650
651     /*
652      * Parse timeControl resource
653      */
654     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
655                           appData.movesPerSession)) {
656         char buf[MSG_SIZ];
657         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
658         DisplayFatalError(buf, 0, 2);
659     }
660
661     /*
662      * Parse searchTime resource
663      */
664     if (*appData.searchTime != NULLCHAR) {
665         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
666         if (matched == 1) {
667             searchTime = min * 60;
668         } else if (matched == 2) {
669             searchTime = min * 60 + sec;
670         } else {
671             char buf[MSG_SIZ];
672             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
673             DisplayFatalError(buf, 0, 2);
674         }
675     }
676
677     /* [AS] Adjudication threshold */
678     adjudicateLossThreshold = appData.adjudicateLossThreshold;
679     
680     first.which = "first";
681     second.which = "second";
682     first.maybeThinking = second.maybeThinking = FALSE;
683     first.pr = second.pr = NoProc;
684     first.isr = second.isr = NULL;
685     first.sendTime = second.sendTime = 2;
686     first.sendDrawOffers = 1;
687     if (appData.firstPlaysBlack) {
688         first.twoMachinesColor = "black\n";
689         second.twoMachinesColor = "white\n";
690     } else {
691         first.twoMachinesColor = "white\n";
692         second.twoMachinesColor = "black\n";
693     }
694     first.program = appData.firstChessProgram;
695     second.program = appData.secondChessProgram;
696     first.host = appData.firstHost;
697     second.host = appData.secondHost;
698     first.dir = appData.firstDirectory;
699     second.dir = appData.secondDirectory;
700     first.other = &second;
701     second.other = &first;
702     first.initString = appData.initString;
703     second.initString = appData.secondInitString;
704     first.computerString = appData.firstComputerString;
705     second.computerString = appData.secondComputerString;
706     first.useSigint = second.useSigint = TRUE;
707     first.useSigterm = second.useSigterm = TRUE;
708     first.reuse = appData.reuseFirst;
709     second.reuse = appData.reuseSecond;
710     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
711     second.nps = appData.secondNPS;
712     first.useSetboard = second.useSetboard = FALSE;
713     first.useSAN = second.useSAN = FALSE;
714     first.usePing = second.usePing = FALSE;
715     first.lastPing = second.lastPing = 0;
716     first.lastPong = second.lastPong = 0;
717     first.usePlayother = second.usePlayother = FALSE;
718     first.useColors = second.useColors = TRUE;
719     first.useUsermove = second.useUsermove = FALSE;
720     first.sendICS = second.sendICS = FALSE;
721     first.sendName = second.sendName = appData.icsActive;
722     first.sdKludge = second.sdKludge = FALSE;
723     first.stKludge = second.stKludge = FALSE;
724     TidyProgramName(first.program, first.host, first.tidy);
725     TidyProgramName(second.program, second.host, second.tidy);
726     first.matchWins = second.matchWins = 0;
727     strcpy(first.variants, appData.variant);
728     strcpy(second.variants, appData.variant);
729     first.analysisSupport = second.analysisSupport = 2; /* detect */
730     first.analyzing = second.analyzing = FALSE;
731     first.initDone = second.initDone = FALSE;
732
733     /* New features added by Tord: */
734     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
735     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
736     /* End of new features added by Tord. */
737     first.fenOverride  = appData.fenOverride1;
738     second.fenOverride = appData.fenOverride2;
739
740     /* [HGM] time odds: set factor for each machine */
741     first.timeOdds  = appData.firstTimeOdds;
742     second.timeOdds = appData.secondTimeOdds;
743     { int norm = 1;
744         if(appData.timeOddsMode) {
745             norm = first.timeOdds;
746             if(norm > second.timeOdds) norm = second.timeOdds;
747         }
748         first.timeOdds /= norm;
749         second.timeOdds /= norm;
750     }
751
752     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
753     first.accumulateTC = appData.firstAccumulateTC;
754     second.accumulateTC = appData.secondAccumulateTC;
755     first.maxNrOfSessions = second.maxNrOfSessions = 1;
756
757     /* [HGM] debug */
758     first.debug = second.debug = FALSE;
759     first.supportsNPS = second.supportsNPS = UNKNOWN;
760
761     /* [HGM] options */
762     first.optionSettings  = appData.firstOptions;
763     second.optionSettings = appData.secondOptions;
764
765     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
766     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
767     first.isUCI = appData.firstIsUCI; /* [AS] */
768     second.isUCI = appData.secondIsUCI; /* [AS] */
769     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
770     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
771
772     if (appData.firstProtocolVersion > PROTOVER ||
773         appData.firstProtocolVersion < 1) {
774       char buf[MSG_SIZ];
775       sprintf(buf, _("protocol version %d not supported"),
776               appData.firstProtocolVersion);
777       DisplayFatalError(buf, 0, 2);
778     } else {
779       first.protocolVersion = appData.firstProtocolVersion;
780     }
781
782     if (appData.secondProtocolVersion > PROTOVER ||
783         appData.secondProtocolVersion < 1) {
784       char buf[MSG_SIZ];
785       sprintf(buf, _("protocol version %d not supported"),
786               appData.secondProtocolVersion);
787       DisplayFatalError(buf, 0, 2);
788     } else {
789       second.protocolVersion = appData.secondProtocolVersion;
790     }
791
792     if (appData.icsActive) {
793         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
794 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
795     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
796         appData.clockMode = FALSE;
797         first.sendTime = second.sendTime = 0;
798     }
799     
800 #if ZIPPY
801     /* Override some settings from environment variables, for backward
802        compatibility.  Unfortunately it's not feasible to have the env
803        vars just set defaults, at least in xboard.  Ugh.
804     */
805     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
806       ZippyInit();
807     }
808 #endif
809     
810     if (appData.noChessProgram) {
811         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812         sprintf(programVersion, "%s", PACKAGE_STRING);
813     } else {
814       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
817     }
818
819     if (!appData.icsActive) {
820       char buf[MSG_SIZ];
821       /* Check for variants that are supported only in ICS mode,
822          or not at all.  Some that are accepted here nevertheless
823          have bugs; see comments below.
824       */
825       VariantClass variant = StringToVariant(appData.variant);
826       switch (variant) {
827       case VariantBughouse:     /* need four players and two boards */
828       case VariantKriegspiel:   /* need to hide pieces and move details */
829       /* case VariantFischeRandom: (Fabien: moved below) */
830         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831         DisplayFatalError(buf, 0, 2);
832         return;
833
834       case VariantUnknown:
835       case VariantLoadable:
836       case Variant29:
837       case Variant30:
838       case Variant31:
839       case Variant32:
840       case Variant33:
841       case Variant34:
842       case Variant35:
843       case Variant36:
844       default:
845         sprintf(buf, _("Unknown variant name %s"), appData.variant);
846         DisplayFatalError(buf, 0, 2);
847         return;
848
849       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
850       case VariantFairy:      /* [HGM] TestLegality definitely off! */
851       case VariantGothic:     /* [HGM] should work */
852       case VariantCapablanca: /* [HGM] should work */
853       case VariantCourier:    /* [HGM] initial forced moves not implemented */
854       case VariantShogi:      /* [HGM] drops not tested for legality */
855       case VariantKnightmate: /* [HGM] should work */
856       case VariantCylinder:   /* [HGM] untested */
857       case VariantFalcon:     /* [HGM] untested */
858       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859                                  offboard interposition not understood */
860       case VariantNormal:     /* definitely works! */
861       case VariantWildCastle: /* pieces not automatically shuffled */
862       case VariantNoCastle:   /* pieces not automatically shuffled */
863       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864       case VariantLosers:     /* should work except for win condition,
865                                  and doesn't know captures are mandatory */
866       case VariantSuicide:    /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantGiveaway:   /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantTwoKings:   /* should work */
871       case VariantAtomic:     /* should work except for win condition */
872       case Variant3Check:     /* should work except for win condition */
873       case VariantShatranj:   /* should work except for all win conditions */
874       case VariantBerolina:   /* might work if TestLegality is off */
875       case VariantCapaRandom: /* should work */
876       case VariantJanus:      /* should work */
877       case VariantSuper:      /* experimental */
878       case VariantGreat:      /* experimental, requires legality testing to be off */
879         break;
880       }
881     }
882
883     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
884     InitEngineUCI( installDir, &second );
885 }
886
887 int NextIntegerFromString( char ** str, long * value )
888 {
889     int result = -1;
890     char * s = *str;
891
892     while( *s == ' ' || *s == '\t' ) {
893         s++;
894     }
895
896     *value = 0;
897
898     if( *s >= '0' && *s <= '9' ) {
899         while( *s >= '0' && *s <= '9' ) {
900             *value = *value * 10 + (*s - '0');
901             s++;
902         }
903
904         result = 0;
905     }
906
907     *str = s;
908
909     return result;
910 }
911
912 int NextTimeControlFromString( char ** str, long * value )
913 {
914     long temp;
915     int result = NextIntegerFromString( str, &temp );
916
917     if( result == 0 ) {
918         *value = temp * 60; /* Minutes */
919         if( **str == ':' ) {
920             (*str)++;
921             result = NextIntegerFromString( str, &temp );
922             *value += temp; /* Seconds */
923         }
924     }
925
926     return result;
927 }
928
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
931     int result = -1; long temp, temp2;
932
933     if(**str != '+') return -1; // old params remain in force!
934     (*str)++;
935     if( NextTimeControlFromString( str, &temp ) ) return -1;
936
937     if(**str != '/') {
938         /* time only: incremental or sudden-death time control */
939         if(**str == '+') { /* increment follows; read it */
940             (*str)++;
941             if(result = NextIntegerFromString( str, &temp2)) return -1;
942             *inc = temp2 * 1000;
943         } else *inc = 0;
944         *moves = 0; *tc = temp * 1000; 
945         return 0;
946     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
947
948     (*str)++; /* classical time control */
949     result = NextTimeControlFromString( str, &temp2);
950     if(result == 0) {
951         *moves = temp/60;
952         *tc    = temp2 * 1000;
953         *inc   = 0;
954     }
955     return result;
956 }
957
958 int GetTimeQuota(int movenr)
959 {   /* [HGM] get time to add from the multi-session time-control string */
960     int moves=1; /* kludge to force reading of first session */
961     long time, increment;
962     char *s = fullTimeControlString;
963
964     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
965     do {
966         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968         if(movenr == -1) return time;    /* last move before new session     */
969         if(!moves) return increment;     /* current session is incremental   */
970         if(movenr >= 0) movenr -= moves; /* we already finished this session */
971     } while(movenr >= -1);               /* try again for next session       */
972
973     return 0; // no new time quota on this move
974 }
975
976 int
977 ParseTimeControl(tc, ti, mps)
978      char *tc;
979      int ti;
980      int mps;
981 {
982   long tc1;
983   long tc2;
984   char buf[MSG_SIZ];
985   
986   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
987   if(ti > 0) {
988     if(mps)
989       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990     else sprintf(buf, "+%s+%d", tc, ti);
991   } else {
992     if(mps)
993              sprintf(buf, "+%d/%s", mps, tc);
994     else sprintf(buf, "+%s", tc);
995   }
996   fullTimeControlString = StrSave(buf);
997   
998   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
999     return FALSE;
1000   }
1001   
1002   if( *tc == '/' ) {
1003     /* Parse second time control */
1004     tc++;
1005     
1006     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1007       return FALSE;
1008     }
1009     
1010     if( tc2 == 0 ) {
1011       return FALSE;
1012     }
1013     
1014     timeControl_2 = tc2 * 1000;
1015   }
1016   else {
1017     timeControl_2 = 0;
1018   }
1019   
1020   if( tc1 == 0 ) {
1021     return FALSE;
1022   }
1023   
1024   timeControl = tc1 * 1000;
1025   
1026   if (ti >= 0) {
1027     timeIncrement = ti * 1000;  /* convert to ms */
1028     movesPerSession = 0;
1029   } else {
1030     timeIncrement = 0;
1031     movesPerSession = mps;
1032   }
1033   return TRUE;
1034 }
1035
1036 void
1037 InitBackEnd2()
1038 {
1039     if (appData.debugMode) {
1040         fprintf(debugFP, "%s\n", programVersion);
1041     }
1042
1043     set_cont_sequence(appData.wrapContSeq);
1044     if (appData.matchGames > 0) {
1045         appData.matchMode = TRUE;
1046     } else if (appData.matchMode) {
1047         appData.matchGames = 1;
1048     }
1049     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1050         appData.matchGames = appData.sameColorGames;
1051     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1052         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1053         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1054     }
1055     Reset(TRUE, FALSE);
1056     if (appData.noChessProgram || first.protocolVersion == 1) {
1057       InitBackEnd3();
1058     } else {
1059       /* kludge: allow timeout for initial "feature" commands */
1060       FreezeUI();
1061       DisplayMessage("", _("Starting chess program"));
1062       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1063     }
1064 }
1065
1066 void
1067 InitBackEnd3 P((void))
1068 {
1069     GameMode initialMode;
1070     char buf[MSG_SIZ];
1071     int err;
1072
1073     InitChessProgram(&first, startedFromSetupPosition);
1074
1075
1076     if (appData.icsActive) {
1077 #ifdef WIN32
1078         /* [DM] Make a console window if needed [HGM] merged ifs */
1079         ConsoleCreate(); 
1080 #endif
1081         err = establish();
1082         if (err != 0) {
1083             if (*appData.icsCommPort != NULLCHAR) {
1084                 sprintf(buf, _("Could not open comm port %s"),  
1085                         appData.icsCommPort);
1086             } else {
1087                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1088                         appData.icsHost, appData.icsPort);
1089             }
1090             DisplayFatalError(buf, err, 1);
1091             return;
1092         }
1093         SetICSMode();
1094         telnetISR =
1095           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1096         fromUserISR =
1097           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1098     } else if (appData.noChessProgram) {
1099         SetNCPMode();
1100     } else {
1101         SetGNUMode();
1102     }
1103
1104     if (*appData.cmailGameName != NULLCHAR) {
1105         SetCmailMode();
1106         OpenLoopback(&cmailPR);
1107         cmailISR =
1108           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1109     }
1110     
1111     ThawUI();
1112     DisplayMessage("", "");
1113     if (StrCaseCmp(appData.initialMode, "") == 0) {
1114       initialMode = BeginningOfGame;
1115     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1116       initialMode = TwoMachinesPlay;
1117     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1118       initialMode = AnalyzeFile; 
1119     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1120       initialMode = AnalyzeMode;
1121     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1122       initialMode = MachinePlaysWhite;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1124       initialMode = MachinePlaysBlack;
1125     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1126       initialMode = EditGame;
1127     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1128       initialMode = EditPosition;
1129     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1130       initialMode = Training;
1131     } else {
1132       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1133       DisplayFatalError(buf, 0, 2);
1134       return;
1135     }
1136
1137     if (appData.matchMode) {
1138         /* Set up machine vs. machine match */
1139         if (appData.noChessProgram) {
1140             DisplayFatalError(_("Can't have a match with no chess programs"),
1141                               0, 2);
1142             return;
1143         }
1144         matchMode = TRUE;
1145         matchGame = 1;
1146         if (*appData.loadGameFile != NULLCHAR) {
1147             int index = appData.loadGameIndex; // [HGM] autoinc
1148             if(index<0) lastIndex = index = 1;
1149             if (!LoadGameFromFile(appData.loadGameFile,
1150                                   index,
1151                                   appData.loadGameFile, FALSE)) {
1152                 DisplayFatalError(_("Bad game file"), 0, 1);
1153                 return;
1154             }
1155         } else if (*appData.loadPositionFile != NULLCHAR) {
1156             int index = appData.loadPositionIndex; // [HGM] autoinc
1157             if(index<0) lastIndex = index = 1;
1158             if (!LoadPositionFromFile(appData.loadPositionFile,
1159                                       index,
1160                                       appData.loadPositionFile)) {
1161                 DisplayFatalError(_("Bad position file"), 0, 1);
1162                 return;
1163             }
1164         }
1165         TwoMachinesEvent();
1166     } else if (*appData.cmailGameName != NULLCHAR) {
1167         /* Set up cmail mode */
1168         ReloadCmailMsgEvent(TRUE);
1169     } else {
1170         /* Set up other modes */
1171         if (initialMode == AnalyzeFile) {
1172           if (*appData.loadGameFile == NULLCHAR) {
1173             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1174             return;
1175           }
1176         }
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             (void) LoadGameFromFile(appData.loadGameFile,
1179                                     appData.loadGameIndex,
1180                                     appData.loadGameFile, TRUE);
1181         } else if (*appData.loadPositionFile != NULLCHAR) {
1182             (void) LoadPositionFromFile(appData.loadPositionFile,
1183                                         appData.loadPositionIndex,
1184                                         appData.loadPositionFile);
1185             /* [HGM] try to make self-starting even after FEN load */
1186             /* to allow automatic setup of fairy variants with wtm */
1187             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1188                 gameMode = BeginningOfGame;
1189                 setboardSpoiledMachineBlack = 1;
1190             }
1191             /* [HGM] loadPos: make that every new game uses the setup */
1192             /* from file as long as we do not switch variant          */
1193             if(!blackPlaysFirst) {
1194                 startedFromPositionFile = TRUE;
1195                 CopyBoard(filePosition, boards[0]);
1196             }
1197         }
1198         if (initialMode == AnalyzeMode) {
1199           if (appData.noChessProgram) {
1200             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1201             return;
1202           }
1203           if (appData.icsActive) {
1204             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1205             return;
1206           }
1207           AnalyzeModeEvent();
1208         } else if (initialMode == AnalyzeFile) {
1209           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210           ShowThinkingEvent();
1211           AnalyzeFileEvent();
1212           AnalysisPeriodicEvent(1);
1213         } else if (initialMode == MachinePlaysWhite) {
1214           if (appData.noChessProgram) {
1215             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1216                               0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1221                               0, 2);
1222             return;
1223           }
1224           MachineWhiteEvent();
1225         } else if (initialMode == MachinePlaysBlack) {
1226           if (appData.noChessProgram) {
1227             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1228                               0, 2);
1229             return;
1230           }
1231           if (appData.icsActive) {
1232             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1233                               0, 2);
1234             return;
1235           }
1236           MachineBlackEvent();
1237         } else if (initialMode == TwoMachinesPlay) {
1238           if (appData.noChessProgram) {
1239             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1240                               0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1245                               0, 2);
1246             return;
1247           }
1248           TwoMachinesEvent();
1249         } else if (initialMode == EditGame) {
1250           EditGameEvent();
1251         } else if (initialMode == EditPosition) {
1252           EditPositionEvent();
1253         } else if (initialMode == Training) {
1254           if (*appData.loadGameFile == NULLCHAR) {
1255             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1256             return;
1257           }
1258           TrainingEvent();
1259         }
1260     }
1261 }
1262
1263 /*
1264  * Establish will establish a contact to a remote host.port.
1265  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266  *  used to talk to the host.
1267  * Returns 0 if okay, error code if not.
1268  */
1269 int
1270 establish()
1271 {
1272     char buf[MSG_SIZ];
1273
1274     if (*appData.icsCommPort != NULLCHAR) {
1275         /* Talk to the host through a serial comm port */
1276         return OpenCommPort(appData.icsCommPort, &icsPR);
1277
1278     } else if (*appData.gateway != NULLCHAR) {
1279         if (*appData.remoteShell == NULLCHAR) {
1280             /* Use the rcmd protocol to run telnet program on a gateway host */
1281             snprintf(buf, sizeof(buf), "%s %s %s",
1282                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1283             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1284
1285         } else {
1286             /* Use the rsh program to run telnet program on a gateway host */
1287             if (*appData.remoteUser == NULLCHAR) {
1288                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289                         appData.gateway, appData.telnetProgram,
1290                         appData.icsHost, appData.icsPort);
1291             } else {
1292                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293                         appData.remoteShell, appData.gateway, 
1294                         appData.remoteUser, appData.telnetProgram,
1295                         appData.icsHost, appData.icsPort);
1296             }
1297             return StartChildProcess(buf, "", &icsPR);
1298
1299         }
1300     } else if (appData.useTelnet) {
1301         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1302
1303     } else {
1304         /* TCP socket interface differs somewhat between
1305            Unix and NT; handle details in the front end.
1306            */
1307         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1308     }
1309 }
1310
1311 void
1312 show_bytes(fp, buf, count)
1313      FILE *fp;
1314      char *buf;
1315      int count;
1316 {
1317     while (count--) {
1318         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319             fprintf(fp, "\\%03o", *buf & 0xff);
1320         } else {
1321             putc(*buf, fp);
1322         }
1323         buf++;
1324     }
1325     fflush(fp);
1326 }
1327
1328 /* Returns an errno value */
1329 int
1330 OutputMaybeTelnet(pr, message, count, outError)
1331      ProcRef pr;
1332      char *message;
1333      int count;
1334      int *outError;
1335 {
1336     char buf[8192], *p, *q, *buflim;
1337     int left, newcount, outcount;
1338
1339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340         *appData.gateway != NULLCHAR) {
1341         if (appData.debugMode) {
1342             fprintf(debugFP, ">ICS: ");
1343             show_bytes(debugFP, message, count);
1344             fprintf(debugFP, "\n");
1345         }
1346         return OutputToProcess(pr, message, count, outError);
1347     }
1348
1349     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1350     p = message;
1351     q = buf;
1352     left = count;
1353     newcount = 0;
1354     while (left) {
1355         if (q >= buflim) {
1356             if (appData.debugMode) {
1357                 fprintf(debugFP, ">ICS: ");
1358                 show_bytes(debugFP, buf, newcount);
1359                 fprintf(debugFP, "\n");
1360             }
1361             outcount = OutputToProcess(pr, buf, newcount, outError);
1362             if (outcount < newcount) return -1; /* to be sure */
1363             q = buf;
1364             newcount = 0;
1365         }
1366         if (*p == '\n') {
1367             *q++ = '\r';
1368             newcount++;
1369         } else if (((unsigned char) *p) == TN_IAC) {
1370             *q++ = (char) TN_IAC;
1371             newcount ++;
1372         }
1373         *q++ = *p++;
1374         newcount++;
1375         left--;
1376     }
1377     if (appData.debugMode) {
1378         fprintf(debugFP, ">ICS: ");
1379         show_bytes(debugFP, buf, newcount);
1380         fprintf(debugFP, "\n");
1381     }
1382     outcount = OutputToProcess(pr, buf, newcount, outError);
1383     if (outcount < newcount) return -1; /* to be sure */
1384     return count;
1385 }
1386
1387 void
1388 read_from_player(isr, closure, message, count, error)
1389      InputSourceRef isr;
1390      VOIDSTAR closure;
1391      char *message;
1392      int count;
1393      int error;
1394 {
1395     int outError, outCount;
1396     static int gotEof = 0;
1397
1398     /* Pass data read from player on to ICS */
1399     if (count > 0) {
1400         gotEof = 0;
1401         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402         if (outCount < count) {
1403             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1404         }
1405     } else if (count < 0) {
1406         RemoveInputSource(isr);
1407         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408     } else if (gotEof++ > 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1411     }
1412 }
1413
1414 void
1415 KeepAlive()
1416 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417     SendToICS("date\n");
1418     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1419 }
1420
1421 /* added routine for printf style output to ics */
1422 void ics_printf(char *format, ...)
1423 {
1424     char buffer[MSG_SIZ];
1425     va_list args;
1426
1427     va_start(args, format);
1428     vsnprintf(buffer, sizeof(buffer), format, args);
1429     buffer[sizeof(buffer)-1] = '\0';
1430     SendToICS(buffer);
1431     va_end(args);
1432 }
1433
1434 void
1435 SendToICS(s)
1436      char *s;
1437 {
1438     int count, outCount, outError;
1439
1440     if (icsPR == NULL) return;
1441
1442     count = strlen(s);
1443     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444     if (outCount < count) {
1445         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446     }
1447 }
1448
1449 /* This is used for sending logon scripts to the ICS. Sending
1450    without a delay causes problems when using timestamp on ICC
1451    (at least on my machine). */
1452 void
1453 SendToICSDelayed(s,msdelay)
1454      char *s;
1455      long msdelay;
1456 {
1457     int count, outCount, outError;
1458
1459     if (icsPR == NULL) return;
1460
1461     count = strlen(s);
1462     if (appData.debugMode) {
1463         fprintf(debugFP, ">ICS: ");
1464         show_bytes(debugFP, s, count);
1465         fprintf(debugFP, "\n");
1466     }
1467     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1468                                       msdelay);
1469     if (outCount < count) {
1470         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1471     }
1472 }
1473
1474
1475 /* Remove all highlighting escape sequences in s
1476    Also deletes any suffix starting with '(' 
1477    */
1478 char *
1479 StripHighlightAndTitle(s)
1480      char *s;
1481 {
1482     static char retbuf[MSG_SIZ];
1483     char *p = retbuf;
1484
1485     while (*s != NULLCHAR) {
1486         while (*s == '\033') {
1487             while (*s != NULLCHAR && !isalpha(*s)) s++;
1488             if (*s != NULLCHAR) s++;
1489         }
1490         while (*s != NULLCHAR && *s != '\033') {
1491             if (*s == '(' || *s == '[') {
1492                 *p = NULLCHAR;
1493                 return retbuf;
1494             }
1495             *p++ = *s++;
1496         }
1497     }
1498     *p = NULLCHAR;
1499     return retbuf;
1500 }
1501
1502 /* Remove all highlighting escape sequences in s */
1503 char *
1504 StripHighlight(s)
1505      char *s;
1506 {
1507     static char retbuf[MSG_SIZ];
1508     char *p = retbuf;
1509
1510     while (*s != NULLCHAR) {
1511         while (*s == '\033') {
1512             while (*s != NULLCHAR && !isalpha(*s)) s++;
1513             if (*s != NULLCHAR) s++;
1514         }
1515         while (*s != NULLCHAR && *s != '\033') {
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 char *variantNames[] = VARIANT_NAMES;
1524 char *
1525 VariantName(v)
1526      VariantClass v;
1527 {
1528     return variantNames[v];
1529 }
1530
1531
1532 /* Identify a variant from the strings the chess servers use or the
1533    PGN Variant tag names we use. */
1534 VariantClass
1535 StringToVariant(e)
1536      char *e;
1537 {
1538     char *p;
1539     int wnum = -1;
1540     VariantClass v = VariantNormal;
1541     int i, found = FALSE;
1542     char buf[MSG_SIZ];
1543
1544     if (!e) return v;
1545
1546     /* [HGM] skip over optional board-size prefixes */
1547     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549         while( *e++ != '_');
1550     }
1551
1552     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1553         v = VariantNormal;
1554         found = TRUE;
1555     } else
1556     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1557       if (StrCaseStr(e, variantNames[i])) {
1558         v = (VariantClass) i;
1559         found = TRUE;
1560         break;
1561       }
1562     }
1563
1564     if (!found) {
1565       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1566           || StrCaseStr(e, "wild/fr") 
1567           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1568         v = VariantFischeRandom;
1569       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1570                  (i = 1, p = StrCaseStr(e, "w"))) {
1571         p += i;
1572         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1573         if (isdigit(*p)) {
1574           wnum = atoi(p);
1575         } else {
1576           wnum = -1;
1577         }
1578         switch (wnum) {
1579         case 0: /* FICS only, actually */
1580         case 1:
1581           /* Castling legal even if K starts on d-file */
1582           v = VariantWildCastle;
1583           break;
1584         case 2:
1585         case 3:
1586         case 4:
1587           /* Castling illegal even if K & R happen to start in
1588              normal positions. */
1589           v = VariantNoCastle;
1590           break;
1591         case 5:
1592         case 7:
1593         case 8:
1594         case 10:
1595         case 11:
1596         case 12:
1597         case 13:
1598         case 14:
1599         case 15:
1600         case 18:
1601         case 19:
1602           /* Castling legal iff K & R start in normal positions */
1603           v = VariantNormal;
1604           break;
1605         case 6:
1606         case 20:
1607         case 21:
1608           /* Special wilds for position setup; unclear what to do here */
1609           v = VariantLoadable;
1610           break;
1611         case 9:
1612           /* Bizarre ICC game */
1613           v = VariantTwoKings;
1614           break;
1615         case 16:
1616           v = VariantKriegspiel;
1617           break;
1618         case 17:
1619           v = VariantLosers;
1620           break;
1621         case 22:
1622           v = VariantFischeRandom;
1623           break;
1624         case 23:
1625           v = VariantCrazyhouse;
1626           break;
1627         case 24:
1628           v = VariantBughouse;
1629           break;
1630         case 25:
1631           v = Variant3Check;
1632           break;
1633         case 26:
1634           /* Not quite the same as FICS suicide! */
1635           v = VariantGiveaway;
1636           break;
1637         case 27:
1638           v = VariantAtomic;
1639           break;
1640         case 28:
1641           v = VariantShatranj;
1642           break;
1643
1644         /* Temporary names for future ICC types.  The name *will* change in 
1645            the next xboard/WinBoard release after ICC defines it. */
1646         case 29:
1647           v = Variant29;
1648           break;
1649         case 30:
1650           v = Variant30;
1651           break;
1652         case 31:
1653           v = Variant31;
1654           break;
1655         case 32:
1656           v = Variant32;
1657           break;
1658         case 33:
1659           v = Variant33;
1660           break;
1661         case 34:
1662           v = Variant34;
1663           break;
1664         case 35:
1665           v = Variant35;
1666           break;
1667         case 36:
1668           v = Variant36;
1669           break;
1670         case 37:
1671           v = VariantShogi;
1672           break;
1673         case 38:
1674           v = VariantXiangqi;
1675           break;
1676         case 39:
1677           v = VariantCourier;
1678           break;
1679         case 40:
1680           v = VariantGothic;
1681           break;
1682         case 41:
1683           v = VariantCapablanca;
1684           break;
1685         case 42:
1686           v = VariantKnightmate;
1687           break;
1688         case 43:
1689           v = VariantFairy;
1690           break;
1691         case 44:
1692           v = VariantCylinder;
1693           break;
1694         case 45:
1695           v = VariantFalcon;
1696           break;
1697         case 46:
1698           v = VariantCapaRandom;
1699           break;
1700         case 47:
1701           v = VariantBerolina;
1702           break;
1703         case 48:
1704           v = VariantJanus;
1705           break;
1706         case 49:
1707           v = VariantSuper;
1708           break;
1709         case 50:
1710           v = VariantGreat;
1711           break;
1712         case -1:
1713           /* Found "wild" or "w" in the string but no number;
1714              must assume it's normal chess. */
1715           v = VariantNormal;
1716           break;
1717         default:
1718           sprintf(buf, _("Unknown wild type %d"), wnum);
1719           DisplayError(buf, 0);
1720           v = VariantUnknown;
1721           break;
1722         }
1723       }
1724     }
1725     if (appData.debugMode) {
1726       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1727               e, wnum, VariantName(v));
1728     }
1729     return v;
1730 }
1731
1732 static int leftover_start = 0, leftover_len = 0;
1733 char star_match[STAR_MATCH_N][MSG_SIZ];
1734
1735 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1736    advance *index beyond it, and set leftover_start to the new value of
1737    *index; else return FALSE.  If pattern contains the character '*', it
1738    matches any sequence of characters not containing '\r', '\n', or the
1739    character following the '*' (if any), and the matched sequence(s) are
1740    copied into star_match.
1741    */
1742 int
1743 looking_at(buf, index, pattern)
1744      char *buf;
1745      int *index;
1746      char *pattern;
1747 {
1748     char *bufp = &buf[*index], *patternp = pattern;
1749     int star_count = 0;
1750     char *matchp = star_match[0];
1751     
1752     for (;;) {
1753         if (*patternp == NULLCHAR) {
1754             *index = leftover_start = bufp - buf;
1755             *matchp = NULLCHAR;
1756             return TRUE;
1757         }
1758         if (*bufp == NULLCHAR) return FALSE;
1759         if (*patternp == '*') {
1760             if (*bufp == *(patternp + 1)) {
1761                 *matchp = NULLCHAR;
1762                 matchp = star_match[++star_count];
1763                 patternp += 2;
1764                 bufp++;
1765                 continue;
1766             } else if (*bufp == '\n' || *bufp == '\r') {
1767                 patternp++;
1768                 if (*patternp == NULLCHAR)
1769                   continue;
1770                 else
1771                   return FALSE;
1772             } else {
1773                 *matchp++ = *bufp++;
1774                 continue;
1775             }
1776         }
1777         if (*patternp != *bufp) return FALSE;
1778         patternp++;
1779         bufp++;
1780     }
1781 }
1782
1783 void
1784 SendToPlayer(data, length)
1785      char *data;
1786      int length;
1787 {
1788     int error, outCount;
1789     outCount = OutputToProcess(NoProc, data, length, &error);
1790     if (outCount < length) {
1791         DisplayFatalError(_("Error writing to display"), error, 1);
1792     }
1793 }
1794
1795 void
1796 PackHolding(packed, holding)
1797      char packed[];
1798      char *holding;
1799 {
1800     char *p = holding;
1801     char *q = packed;
1802     int runlength = 0;
1803     int curr = 9999;
1804     do {
1805         if (*p == curr) {
1806             runlength++;
1807         } else {
1808             switch (runlength) {
1809               case 0:
1810                 break;
1811               case 1:
1812                 *q++ = curr;
1813                 break;
1814               case 2:
1815                 *q++ = curr;
1816                 *q++ = curr;
1817                 break;
1818               default:
1819                 sprintf(q, "%d", runlength);
1820                 while (*q) q++;
1821                 *q++ = curr;
1822                 break;
1823             }
1824             runlength = 1;
1825             curr = *p;
1826         }
1827     } while (*p++);
1828     *q = NULLCHAR;
1829 }
1830
1831 /* Telnet protocol requests from the front end */
1832 void
1833 TelnetRequest(ddww, option)
1834      unsigned char ddww, option;
1835 {
1836     unsigned char msg[3];
1837     int outCount, outError;
1838
1839     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1840
1841     if (appData.debugMode) {
1842         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1843         switch (ddww) {
1844           case TN_DO:
1845             ddwwStr = "DO";
1846             break;
1847           case TN_DONT:
1848             ddwwStr = "DONT";
1849             break;
1850           case TN_WILL:
1851             ddwwStr = "WILL";
1852             break;
1853           case TN_WONT:
1854             ddwwStr = "WONT";
1855             break;
1856           default:
1857             ddwwStr = buf1;
1858             sprintf(buf1, "%d", ddww);
1859             break;
1860         }
1861         switch (option) {
1862           case TN_ECHO:
1863             optionStr = "ECHO";
1864             break;
1865           default:
1866             optionStr = buf2;
1867             sprintf(buf2, "%d", option);
1868             break;
1869         }
1870         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1871     }
1872     msg[0] = TN_IAC;
1873     msg[1] = ddww;
1874     msg[2] = option;
1875     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1876     if (outCount < 3) {
1877         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1878     }
1879 }
1880
1881 void
1882 DoEcho()
1883 {
1884     if (!appData.icsActive) return;
1885     TelnetRequest(TN_DO, TN_ECHO);
1886 }
1887
1888 void
1889 DontEcho()
1890 {
1891     if (!appData.icsActive) return;
1892     TelnetRequest(TN_DONT, TN_ECHO);
1893 }
1894
1895 void
1896 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1897 {
1898     /* put the holdings sent to us by the server on the board holdings area */
1899     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1900     char p;
1901     ChessSquare piece;
1902
1903     if(gameInfo.holdingsWidth < 2)  return;
1904     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1905         return; // prevent overwriting by pre-board holdings
1906
1907     if( (int)lowestPiece >= BlackPawn ) {
1908         holdingsColumn = 0;
1909         countsColumn = 1;
1910         holdingsStartRow = BOARD_HEIGHT-1;
1911         direction = -1;
1912     } else {
1913         holdingsColumn = BOARD_WIDTH-1;
1914         countsColumn = BOARD_WIDTH-2;
1915         holdingsStartRow = 0;
1916         direction = 1;
1917     }
1918
1919     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920         board[i][holdingsColumn] = EmptySquare;
1921         board[i][countsColumn]   = (ChessSquare) 0;
1922     }
1923     while( (p=*holdings++) != NULLCHAR ) {
1924         piece = CharToPiece( ToUpper(p) );
1925         if(piece == EmptySquare) continue;
1926         /*j = (int) piece - (int) WhitePawn;*/
1927         j = PieceToNumber(piece);
1928         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929         if(j < 0) continue;               /* should not happen */
1930         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932         board[holdingsStartRow+j*direction][countsColumn]++;
1933     }
1934 }
1935
1936
1937 void
1938 VariantSwitch(Board board, VariantClass newVariant)
1939 {
1940    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1941    Board oldBoard;
1942
1943    startedFromPositionFile = FALSE;
1944    if(gameInfo.variant == newVariant) return;
1945
1946    /* [HGM] This routine is called each time an assignment is made to
1947     * gameInfo.variant during a game, to make sure the board sizes
1948     * are set to match the new variant. If that means adding or deleting
1949     * holdings, we shift the playing board accordingly
1950     * This kludge is needed because in ICS observe mode, we get boards
1951     * of an ongoing game without knowing the variant, and learn about the
1952     * latter only later. This can be because of the move list we requested,
1953     * in which case the game history is refilled from the beginning anyway,
1954     * but also when receiving holdings of a crazyhouse game. In the latter
1955     * case we want to add those holdings to the already received position.
1956     */
1957
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        break;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013    } else gameInfo.variant = newVariant;
2014    CopyBoard(oldBoard, board);   // remember correctly formatted board
2015      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2016    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     static int cmatch = 0; // continuation sequence match
2060     char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2082                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][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), TRUE);
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                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3262                         if (looking_at(buf, &i, "*% ")) {
3263                             savingComment = FALSE;
3264                         }
3265                     }
3266                     next_out = i;
3267                 } else if (started == STARTED_HOLDINGS) {
3268                     int gamenum;
3269                     char new_piece[MSG_SIZ];
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     if (appData.debugMode)
3273                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274                                                         parse, currentMove);
3275                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276                         gamenum == ics_gamenum) {
3277                         if (gameInfo.variant == VariantNormal) {
3278                           /* [HGM] We seem to switch variant during a game!
3279                            * Presumably no holdings were displayed, so we have
3280                            * to move the position two files to the right to
3281                            * create room for them!
3282                            */
3283                           VariantClass newVariant;
3284                           switch(gameInfo.boardWidth) { // base guess on board width
3285                                 case 9:  newVariant = VariantShogi; break;
3286                                 case 10: newVariant = VariantGreat; break;
3287                                 default: newVariant = VariantCrazyhouse; break;
3288                           }
3289                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3290                           /* Get a move list just to see the header, which
3291                              will tell us whether this is really bug or zh */
3292                           if (ics_getting_history == H_FALSE) {
3293                             ics_getting_history = H_REQUESTED;
3294                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3295                             SendToICS(str);
3296                           }
3297                         }
3298                         new_piece[0] = NULLCHAR;
3299                         sscanf(parse, "game %d white [%s black [%s <- %s",
3300                                &gamenum, white_holding, black_holding,
3301                                new_piece);
3302                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3303                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3304                         /* [HGM] copy holdings to board holdings area */
3305                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3306                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3307                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3308 #if ZIPPY
3309                         if (appData.zippyPlay && first.initDone) {
3310                             ZippyHoldings(white_holding, black_holding,
3311                                           new_piece);
3312                         }
3313 #endif /*ZIPPY*/
3314                         if (tinyLayout || smallLayout) {
3315                             char wh[16], bh[16];
3316                             PackHolding(wh, white_holding);
3317                             PackHolding(bh, black_holding);
3318                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3319                                     gameInfo.white, gameInfo.black);
3320                         } else {
3321                             sprintf(str, "%s [%s] vs. %s [%s]",
3322                                     gameInfo.white, white_holding,
3323                                     gameInfo.black, black_holding);
3324                         }
3325
3326                         DrawPosition(FALSE, boards[currentMove]);
3327                         DisplayTitle(str);
3328                     }
3329                     /* Suppress following prompt */
3330                     if (looking_at(buf, &i, "*% ")) {
3331                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3332                         savingComment = FALSE;
3333                     }
3334                     next_out = i;
3335                 }
3336                 continue;
3337             }
3338
3339             i++;                /* skip unparsed character and loop back */
3340         }
3341         
3342         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3343             started != STARTED_HOLDINGS && i > next_out) {
3344             SendToPlayer(&buf[next_out], i - next_out);
3345             next_out = i;
3346         }
3347         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3348         
3349         leftover_len = buf_len - leftover_start;
3350         /* if buffer ends with something we couldn't parse,
3351            reparse it after appending the next read */
3352         
3353     } else if (count == 0) {
3354         RemoveInputSource(isr);
3355         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3356     } else {
3357         DisplayFatalError(_("Error reading from ICS"), error, 1);
3358     }
3359 }
3360
3361
3362 /* Board style 12 looks like this:
3363    
3364    <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
3365    
3366  * The "<12> " is stripped before it gets to this routine.  The two
3367  * trailing 0's (flip state and clock ticking) are later addition, and
3368  * some chess servers may not have them, or may have only the first.
3369  * Additional trailing fields may be added in the future.  
3370  */
3371
3372 #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"
3373
3374 #define RELATION_OBSERVING_PLAYED    0
3375 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3376 #define RELATION_PLAYING_MYMOVE      1
3377 #define RELATION_PLAYING_NOTMYMOVE  -1
3378 #define RELATION_EXAMINING           2
3379 #define RELATION_ISOLATED_BOARD     -3
3380 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3381
3382 void
3383 ParseBoard12(string)
3384      char *string;
3385
3386     GameMode newGameMode;
3387     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3388     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3389     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3390     char to_play, board_chars[200];
3391     char move_str[500], str[500], elapsed_time[500];
3392     char black[32], white[32];
3393     Board board;
3394     int prevMove = currentMove;
3395     int ticking = 2;
3396     ChessMove moveType;
3397     int fromX, fromY, toX, toY;
3398     char promoChar;
3399     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3400     char *bookHit = NULL; // [HGM] book
3401     Boolean weird = FALSE, reqFlag = FALSE;
3402
3403     fromX = fromY = toX = toY = -1;
3404     
3405     newGame = FALSE;
3406
3407     if (appData.debugMode)
3408       fprintf(debugFP, _("Parsing board: %s\n"), string);
3409
3410     move_str[0] = NULLCHAR;
3411     elapsed_time[0] = NULLCHAR;
3412     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3413         int  i = 0, j;
3414         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3415             if(string[i] == ' ') { ranks++; files = 0; }
3416             else files++;
3417             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3418             i++;
3419         }
3420         for(j = 0; j <i; j++) board_chars[j] = string[j];
3421         board_chars[i] = '\0';
3422         string += i + 1;
3423     }
3424     n = sscanf(string, PATTERN, &to_play, &double_push,
3425                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3426                &gamenum, white, black, &relation, &basetime, &increment,
3427                &white_stren, &black_stren, &white_time, &black_time,
3428                &moveNum, str, elapsed_time, move_str, &ics_flip,
3429                &ticking);
3430
3431     if (n < 21) {
3432         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3433         DisplayError(str, 0);
3434         return;
3435     }
3436
3437     /* Convert the move number to internal form */
3438     moveNum = (moveNum - 1) * 2;
3439     if (to_play == 'B') moveNum++;
3440     if (moveNum >= MAX_MOVES) {
3441       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3442                         0, 1);
3443       return;
3444     }
3445     
3446     switch (relation) {
3447       case RELATION_OBSERVING_PLAYED:
3448       case RELATION_OBSERVING_STATIC:
3449         if (gamenum == -1) {
3450             /* Old ICC buglet */
3451             relation = RELATION_OBSERVING_STATIC;
3452         }
3453         newGameMode = IcsObserving;
3454         break;
3455       case RELATION_PLAYING_MYMOVE:
3456       case RELATION_PLAYING_NOTMYMOVE:
3457         newGameMode =
3458           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3459             IcsPlayingWhite : IcsPlayingBlack;
3460         break;
3461       case RELATION_EXAMINING:
3462         newGameMode = IcsExamining;
3463         break;
3464       case RELATION_ISOLATED_BOARD:
3465       default:
3466         /* Just display this board.  If user was doing something else,
3467            we will forget about it until the next board comes. */ 
3468         newGameMode = IcsIdle;
3469         break;
3470       case RELATION_STARTING_POSITION:
3471         newGameMode = gameMode;
3472         break;
3473     }
3474     
3475     /* Modify behavior for initial board display on move listing
3476        of wild games.
3477        */
3478     switch (ics_getting_history) {
3479       case H_FALSE:
3480       case H_REQUESTED:
3481         break;
3482       case H_GOT_REQ_HEADER:
3483       case H_GOT_UNREQ_HEADER:
3484         /* This is the initial position of the current game */
3485         gamenum = ics_gamenum;
3486         moveNum = 0;            /* old ICS bug workaround */
3487         if (to_play == 'B') {
3488           startedFromSetupPosition = TRUE;
3489           blackPlaysFirst = TRUE;
3490           moveNum = 1;
3491           if (forwardMostMove == 0) forwardMostMove = 1;
3492           if (backwardMostMove == 0) backwardMostMove = 1;
3493           if (currentMove == 0) currentMove = 1;
3494         }
3495         newGameMode = gameMode;
3496         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3497         break;
3498       case H_GOT_UNWANTED_HEADER:
3499         /* This is an initial board that we don't want */
3500         return;
3501       case H_GETTING_MOVES:
3502         /* Should not happen */
3503         DisplayError(_("Error gathering move list: extra board"), 0);
3504         ics_getting_history = H_FALSE;
3505         return;
3506     }
3507
3508    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3509                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3510      /* [HGM] We seem to have switched variant unexpectedly
3511       * Try to guess new variant from board size
3512       */
3513           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3514           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3515           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3516           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3517           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3518           if(!weird) newVariant = VariantNormal;
3519           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3520           /* Get a move list just to see the header, which
3521              will tell us whether this is really bug or zh */
3522           if (ics_getting_history == H_FALSE) {
3523             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3524             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3525             SendToICS(str);
3526           }
3527     }
3528     
3529     /* Take action if this is the first board of a new game, or of a
3530        different game than is currently being displayed.  */
3531     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3532         relation == RELATION_ISOLATED_BOARD) {
3533         
3534         /* Forget the old game and get the history (if any) of the new one */
3535         if (gameMode != BeginningOfGame) {
3536           Reset(TRUE, TRUE);
3537         }
3538         newGame = TRUE;
3539         if (appData.autoRaiseBoard) BoardToTop();
3540         prevMove = -3;
3541         if (gamenum == -1) {
3542             newGameMode = IcsIdle;
3543         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3544                    appData.getMoveList && !reqFlag) {
3545             /* Need to get game history */
3546             ics_getting_history = H_REQUESTED;
3547             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3548             SendToICS(str);
3549         }
3550         
3551         /* Initially flip the board to have black on the bottom if playing
3552            black or if the ICS flip flag is set, but let the user change
3553            it with the Flip View button. */
3554         flipView = appData.autoFlipView ? 
3555           (newGameMode == IcsPlayingBlack) || ics_flip :
3556           appData.flipView;
3557         
3558         /* Done with values from previous mode; copy in new ones */
3559         gameMode = newGameMode;
3560         ModeHighlight();
3561         ics_gamenum = gamenum;
3562         if (gamenum == gs_gamenum) {
3563             int klen = strlen(gs_kind);
3564             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3565             sprintf(str, "ICS %s", gs_kind);
3566             gameInfo.event = StrSave(str);
3567         } else {
3568             gameInfo.event = StrSave("ICS game");
3569         }
3570         gameInfo.site = StrSave(appData.icsHost);
3571         gameInfo.date = PGNDate();
3572         gameInfo.round = StrSave("-");
3573         gameInfo.white = StrSave(white);
3574         gameInfo.black = StrSave(black);
3575         timeControl = basetime * 60 * 1000;
3576         timeControl_2 = 0;
3577         timeIncrement = increment * 1000;
3578         movesPerSession = 0;
3579         gameInfo.timeControl = TimeControlTagValue();
3580         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3581   if (appData.debugMode) {
3582     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3583     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3584     setbuf(debugFP, NULL);
3585   }
3586
3587         gameInfo.outOfBook = NULL;
3588         
3589         /* Do we have the ratings? */
3590         if (strcmp(player1Name, white) == 0 &&
3591             strcmp(player2Name, black) == 0) {
3592             if (appData.debugMode)
3593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3594                       player1Rating, player2Rating);
3595             gameInfo.whiteRating = player1Rating;
3596             gameInfo.blackRating = player2Rating;
3597         } else if (strcmp(player2Name, white) == 0 &&
3598                    strcmp(player1Name, black) == 0) {
3599             if (appData.debugMode)
3600               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3601                       player2Rating, player1Rating);
3602             gameInfo.whiteRating = player2Rating;
3603             gameInfo.blackRating = player1Rating;
3604         }
3605         player1Name[0] = player2Name[0] = NULLCHAR;
3606
3607         /* Silence shouts if requested */
3608         if (appData.quietPlay &&
3609             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3610             SendToICS(ics_prefix);
3611             SendToICS("set shout 0\n");
3612         }
3613     }
3614     
3615     /* Deal with midgame name changes */
3616     if (!newGame) {
3617         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3618             if (gameInfo.white) free(gameInfo.white);
3619             gameInfo.white = StrSave(white);
3620         }
3621         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3622             if (gameInfo.black) free(gameInfo.black);
3623             gameInfo.black = StrSave(black);
3624         }
3625     }
3626     
3627     /* Throw away game result if anything actually changes in examine mode */
3628     if (gameMode == IcsExamining && !newGame) {
3629         gameInfo.result = GameUnfinished;
3630         if (gameInfo.resultDetails != NULL) {
3631             free(gameInfo.resultDetails);
3632             gameInfo.resultDetails = NULL;
3633         }
3634     }
3635     
3636     /* In pausing && IcsExamining mode, we ignore boards coming
3637        in if they are in a different variation than we are. */
3638     if (pauseExamInvalid) return;
3639     if (pausing && gameMode == IcsExamining) {
3640         if (moveNum <= pauseExamForwardMostMove) {
3641             pauseExamInvalid = TRUE;
3642             forwardMostMove = pauseExamForwardMostMove;
3643             return;
3644         }
3645     }
3646     
3647   if (appData.debugMode) {
3648     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3649   }
3650     /* Parse the board */
3651     for (k = 0; k < ranks; k++) {
3652       for (j = 0; j < files; j++)
3653         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3654       if(gameInfo.holdingsWidth > 1) {
3655            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3656            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3657       }
3658     }
3659     CopyBoard(boards[moveNum], board);
3660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3661     if (moveNum == 0) {
3662         startedFromSetupPosition =
3663           !CompareBoards(board, initialPosition);
3664         if(startedFromSetupPosition)
3665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3666     }
3667
3668     /* [HGM] Set castling rights. Take the outermost Rooks,
3669        to make it also work for FRC opening positions. Note that board12
3670        is really defective for later FRC positions, as it has no way to
3671        indicate which Rook can castle if they are on the same side of King.
3672        For the initial position we grant rights to the outermost Rooks,
3673        and remember thos rights, and we then copy them on positions
3674        later in an FRC game. This means WB might not recognize castlings with
3675        Rooks that have moved back to their original position as illegal,
3676        but in ICS mode that is not its job anyway.
3677     */
3678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3680
3681         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3682             if(board[0][i] == WhiteRook) j = i;
3683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3684         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3685             if(board[0][i] == WhiteRook) j = i;
3686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3687         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3690         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3693
3694         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3698             if(board[BOARD_HEIGHT-1][k] == bKing)
3699                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3700     } else { int r;
3701         r = boards[moveNum][CASTLING][0] = initialRights[0];
3702         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3703         r = boards[moveNum][CASTLING][1] = initialRights[1];
3704         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3705         r = boards[moveNum][CASTLING][3] = initialRights[3];
3706         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3707         r = boards[moveNum][CASTLING][4] = initialRights[4];
3708         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3709         /* wildcastle kludge: always assume King has rights */
3710         r = boards[moveNum][CASTLING][2] = initialRights[2];
3711         r = boards[moveNum][CASTLING][5] = initialRights[5];
3712     }
3713     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3714     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3715
3716     
3717     if (ics_getting_history == H_GOT_REQ_HEADER ||
3718         ics_getting_history == H_GOT_UNREQ_HEADER) {
3719         /* This was an initial position from a move list, not
3720            the current position */
3721         return;
3722     }
3723     
3724     /* Update currentMove and known move number limits */
3725     newMove = newGame || moveNum > forwardMostMove;
3726
3727     if (newGame) {
3728         forwardMostMove = backwardMostMove = currentMove = moveNum;
3729         if (gameMode == IcsExamining && moveNum == 0) {
3730           /* Workaround for ICS limitation: we are not told the wild
3731              type when starting to examine a game.  But if we ask for
3732              the move list, the move list header will tell us */
3733             ics_getting_history = H_REQUESTED;
3734             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3735             SendToICS(str);
3736         }
3737     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3738                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3739 #if ZIPPY
3740         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3741         /* [HGM] applied this also to an engine that is silently watching        */
3742         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3743             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3744             gameInfo.variant == currentlyInitializedVariant) {
3745           takeback = forwardMostMove - moveNum;
3746           for (i = 0; i < takeback; i++) {
3747             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3748             SendToProgram("undo\n", &first);
3749           }
3750         }
3751 #endif
3752
3753         forwardMostMove = moveNum;
3754         if (!pausing || currentMove > forwardMostMove)
3755           currentMove = forwardMostMove;
3756     } else {
3757         /* New part of history that is not contiguous with old part */ 
3758         if (pausing && gameMode == IcsExamining) {
3759             pauseExamInvalid = TRUE;
3760             forwardMostMove = pauseExamForwardMostMove;
3761             return;
3762         }
3763         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3764 #if ZIPPY
3765             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3766                 // [HGM] when we will receive the move list we now request, it will be
3767                 // fed to the engine from the first move on. So if the engine is not
3768                 // in the initial position now, bring it there.
3769                 InitChessProgram(&first, 0);
3770             }
3771 #endif
3772             ics_getting_history = H_REQUESTED;
3773             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3774             SendToICS(str);
3775         }
3776         forwardMostMove = backwardMostMove = currentMove = moveNum;
3777     }
3778     
3779     /* Update the clocks */
3780     if (strchr(elapsed_time, '.')) {
3781       /* Time is in ms */
3782       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3783       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3784     } else {
3785       /* Time is in seconds */
3786       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3787       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3788     }
3789       
3790
3791 #if ZIPPY
3792     if (appData.zippyPlay && newGame &&
3793         gameMode != IcsObserving && gameMode != IcsIdle &&
3794         gameMode != IcsExamining)
3795       ZippyFirstBoard(moveNum, basetime, increment);
3796 #endif
3797     
3798     /* Put the move on the move list, first converting
3799        to canonical algebraic form. */
3800     if (moveNum > 0) {
3801   if (appData.debugMode) {
3802     if (appData.debugMode) { int f = forwardMostMove;
3803         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3804                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3805                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3806     }
3807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808     fprintf(debugFP, "moveNum = %d\n", moveNum);
3809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810     setbuf(debugFP, NULL);
3811   }
3812         if (moveNum <= backwardMostMove) {
3813             /* We don't know what the board looked like before
3814                this move.  Punt. */
3815             strcpy(parseList[moveNum - 1], move_str);
3816             strcat(parseList[moveNum - 1], " ");
3817             strcat(parseList[moveNum - 1], elapsed_time);
3818             moveList[moveNum - 1][0] = NULLCHAR;
3819         } else if (strcmp(move_str, "none") == 0) {
3820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821             /* Again, we don't know what the board looked like;
3822                this is really the start of the game. */
3823             parseList[moveNum - 1][0] = NULLCHAR;
3824             moveList[moveNum - 1][0] = NULLCHAR;
3825             backwardMostMove = moveNum;
3826             startedFromSetupPosition = TRUE;
3827             fromX = fromY = toX = toY = -1;
3828         } else {
3829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3830           //                 So we parse the long-algebraic move string in stead of the SAN move
3831           int valid; char buf[MSG_SIZ], *prom;
3832
3833           // str looks something like "Q/a1-a2"; kill the slash
3834           if(str[1] == '/') 
3835                 sprintf(buf, "%c%s", str[0], str+2);
3836           else  strcpy(buf, str); // might be castling
3837           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3838                 strcat(buf, prom); // long move lacks promo specification!
3839           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840                 if(appData.debugMode) 
3841                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842                 strcpy(move_str, buf);
3843           }
3844           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845                                 &fromX, &fromY, &toX, &toY, &promoChar)
3846                || ParseOneMove(buf, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar);
3848           // end of long SAN patch
3849           if (valid) {
3850             (void) CoordsToAlgebraic(boards[moveNum - 1],
3851                                      PosFlags(moveNum - 1),
3852                                      fromY, fromX, toY, toX, promoChar,
3853                                      parseList[moveNum-1]);
3854             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3855               case MT_NONE:
3856               case MT_STALEMATE:
3857               default:
3858                 break;
3859               case MT_CHECK:
3860                 if(gameInfo.variant != VariantShogi)
3861                     strcat(parseList[moveNum - 1], "+");
3862                 break;
3863               case MT_CHECKMATE:
3864               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3865                 strcat(parseList[moveNum - 1], "#");
3866                 break;
3867             }
3868             strcat(parseList[moveNum - 1], " ");
3869             strcat(parseList[moveNum - 1], elapsed_time);
3870             /* currentMoveString is set as a side-effect of ParseOneMove */
3871             strcpy(moveList[moveNum - 1], currentMoveString);
3872             strcat(moveList[moveNum - 1], "\n");
3873           } else {
3874             /* Move from ICS was illegal!?  Punt. */
3875   if (appData.debugMode) {
3876     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3877     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3878   }
3879             strcpy(parseList[moveNum - 1], move_str);
3880             strcat(parseList[moveNum - 1], " ");
3881             strcat(parseList[moveNum - 1], elapsed_time);
3882             moveList[moveNum - 1][0] = NULLCHAR;
3883             fromX = fromY = toX = toY = -1;
3884           }
3885         }
3886   if (appData.debugMode) {
3887     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3888     setbuf(debugFP, NULL);
3889   }
3890
3891 #if ZIPPY
3892         /* Send move to chess program (BEFORE animating it). */
3893         if (appData.zippyPlay && !newGame && newMove && 
3894            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3895
3896             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3897                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3898                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3899                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3900                             move_str);
3901                     DisplayError(str, 0);
3902                 } else {
3903                     if (first.sendTime) {
3904                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3905                     }
3906                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3907                     if (firstMove && !bookHit) {
3908                         firstMove = FALSE;
3909                         if (first.useColors) {
3910                           SendToProgram(gameMode == IcsPlayingWhite ?
3911                                         "white\ngo\n" :
3912                                         "black\ngo\n", &first);
3913                         } else {
3914                           SendToProgram("go\n", &first);
3915                         }
3916                         first.maybeThinking = TRUE;
3917                     }
3918                 }
3919             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3920               if (moveList[moveNum - 1][0] == NULLCHAR) {
3921                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3922                 DisplayError(str, 0);
3923               } else {
3924                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3925                 SendMoveToProgram(moveNum - 1, &first);
3926               }
3927             }
3928         }
3929 #endif
3930     }
3931
3932     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3933         /* If move comes from a remote source, animate it.  If it
3934            isn't remote, it will have already been animated. */
3935         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3936             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3937         }
3938         if (!pausing && appData.highlightLastMove) {
3939             SetHighlights(fromX, fromY, toX, toY);
3940         }
3941     }
3942     
3943     /* Start the clocks */
3944     whiteFlag = blackFlag = FALSE;
3945     appData.clockMode = !(basetime == 0 && increment == 0);
3946     if (ticking == 0) {
3947       ics_clock_paused = TRUE;
3948       StopClocks();
3949     } else if (ticking == 1) {
3950       ics_clock_paused = FALSE;
3951     }
3952     if (gameMode == IcsIdle ||
3953         relation == RELATION_OBSERVING_STATIC ||
3954         relation == RELATION_EXAMINING ||
3955         ics_clock_paused)
3956       DisplayBothClocks();
3957     else
3958       StartClocks();
3959     
3960     /* Display opponents and material strengths */
3961     if (gameInfo.variant != VariantBughouse &&
3962         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3963         if (tinyLayout || smallLayout) {
3964             if(gameInfo.variant == VariantNormal)
3965                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3966                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3967                     basetime, increment);
3968             else
3969                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3970                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3971                     basetime, increment, (int) gameInfo.variant);
3972         } else {
3973             if(gameInfo.variant == VariantNormal)
3974                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3975                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3976                     basetime, increment);
3977             else
3978                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3980                     basetime, increment, VariantName(gameInfo.variant));
3981         }
3982         DisplayTitle(str);
3983   if (appData.debugMode) {
3984     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3985   }
3986     }
3987
3988    
3989     /* Display the board */
3990     if (!pausing && !appData.noGUI) {
3991       
3992       if (appData.premove)
3993           if (!gotPremove || 
3994              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3995              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3996               ClearPremoveHighlights();
3997
3998       DrawPosition(FALSE, boards[currentMove]);
3999       DisplayMove(moveNum - 1);
4000       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4001             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4002               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4003         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4004       }
4005     }
4006
4007     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4008 #if ZIPPY
4009     if(bookHit) { // [HGM] book: simulate book reply
4010         static char bookMove[MSG_SIZ]; // a bit generous?
4011
4012         programStats.nodes = programStats.depth = programStats.time = 
4013         programStats.score = programStats.got_only_move = 0;
4014         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4015
4016         strcpy(bookMove, "move ");
4017         strcat(bookMove, bookHit);
4018         HandleMachineMove(bookMove, &first);
4019     }
4020 #endif
4021 }
4022
4023 void
4024 GetMoveListEvent()
4025 {
4026     char buf[MSG_SIZ];
4027     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4028         ics_getting_history = H_REQUESTED;
4029         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4030         SendToICS(buf);
4031     }
4032 }
4033
4034 void
4035 AnalysisPeriodicEvent(force)
4036      int force;
4037 {
4038     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4039          && !force) || !appData.periodicUpdates)
4040       return;
4041
4042     /* Send . command to Crafty to collect stats */
4043     SendToProgram(".\n", &first);
4044
4045     /* Don't send another until we get a response (this makes
4046        us stop sending to old Crafty's which don't understand
4047        the "." command (sending illegal cmds resets node count & time,
4048        which looks bad)) */
4049     programStats.ok_to_send = 0;
4050 }
4051
4052 void ics_update_width(new_width)
4053         int new_width;
4054 {
4055         ics_printf("set width %d\n", new_width);
4056 }
4057
4058 void
4059 SendMoveToProgram(moveNum, cps)
4060      int moveNum;
4061      ChessProgramState *cps;
4062 {
4063     char buf[MSG_SIZ];
4064
4065     if (cps->useUsermove) {
4066       SendToProgram("usermove ", cps);
4067     }
4068     if (cps->useSAN) {
4069       char *space;
4070       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4071         int len = space - parseList[moveNum];
4072         memcpy(buf, parseList[moveNum], len);
4073         buf[len++] = '\n';
4074         buf[len] = NULLCHAR;
4075       } else {
4076         sprintf(buf, "%s\n", parseList[moveNum]);
4077       }
4078       SendToProgram(buf, cps);
4079     } else {
4080       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4081         AlphaRank(moveList[moveNum], 4);
4082         SendToProgram(moveList[moveNum], cps);
4083         AlphaRank(moveList[moveNum], 4); // and back
4084       } else
4085       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4086        * the engine. It would be nice to have a better way to identify castle 
4087        * moves here. */
4088       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4089                                                                          && cps->useOOCastle) {
4090         int fromX = moveList[moveNum][0] - AAA; 
4091         int fromY = moveList[moveNum][1] - ONE;
4092         int toX = moveList[moveNum][2] - AAA; 
4093         int toY = moveList[moveNum][3] - ONE;
4094         if((boards[moveNum][fromY][fromX] == WhiteKing 
4095             && boards[moveNum][toY][toX] == WhiteRook)
4096            || (boards[moveNum][fromY][fromX] == BlackKing 
4097                && boards[moveNum][toY][toX] == BlackRook)) {
4098           if(toX > fromX) SendToProgram("O-O\n", cps);
4099           else SendToProgram("O-O-O\n", cps);
4100         }
4101         else SendToProgram(moveList[moveNum], cps);
4102       }
4103       else SendToProgram(moveList[moveNum], cps);
4104       /* End of additions by Tord */
4105     }
4106
4107     /* [HGM] setting up the opening has brought engine in force mode! */
4108     /*       Send 'go' if we are in a mode where machine should play. */
4109     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4110         (gameMode == TwoMachinesPlay   ||
4111 #ifdef ZIPPY
4112          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4113 #endif
4114          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4115         SendToProgram("go\n", cps);
4116   if (appData.debugMode) {
4117     fprintf(debugFP, "(extra)\n");
4118   }
4119     }
4120     setboardSpoiledMachineBlack = 0;
4121 }
4122
4123 void
4124 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4125      ChessMove moveType;
4126      int fromX, fromY, toX, toY;
4127 {
4128     char user_move[MSG_SIZ];
4129
4130     switch (moveType) {
4131       default:
4132         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4133                 (int)moveType, fromX, fromY, toX, toY);
4134         DisplayError(user_move + strlen("say "), 0);
4135         break;
4136       case WhiteKingSideCastle:
4137       case BlackKingSideCastle:
4138       case WhiteQueenSideCastleWild:
4139       case BlackQueenSideCastleWild:
4140       /* PUSH Fabien */
4141       case WhiteHSideCastleFR:
4142       case BlackHSideCastleFR:
4143       /* POP Fabien */
4144         sprintf(user_move, "o-o\n");
4145         break;
4146       case WhiteQueenSideCastle:
4147       case BlackQueenSideCastle:
4148       case WhiteKingSideCastleWild:
4149       case BlackKingSideCastleWild:
4150       /* PUSH Fabien */
4151       case WhiteASideCastleFR:
4152       case BlackASideCastleFR:
4153       /* POP Fabien */
4154         sprintf(user_move, "o-o-o\n");
4155         break;
4156       case WhitePromotionQueen:
4157       case BlackPromotionQueen:
4158       case WhitePromotionRook:
4159       case BlackPromotionRook:
4160       case WhitePromotionBishop:
4161       case BlackPromotionBishop:
4162       case WhitePromotionKnight:
4163       case BlackPromotionKnight:
4164       case WhitePromotionKing:
4165       case BlackPromotionKing:
4166       case WhitePromotionChancellor:
4167       case BlackPromotionChancellor:
4168       case WhitePromotionArchbishop:
4169       case BlackPromotionArchbishop:
4170         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4171             sprintf(user_move, "%c%c%c%c=%c\n",
4172                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173                 PieceToChar(WhiteFerz));
4174         else if(gameInfo.variant == VariantGreat)
4175             sprintf(user_move, "%c%c%c%c=%c\n",
4176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177                 PieceToChar(WhiteMan));
4178         else
4179             sprintf(user_move, "%c%c%c%c=%c\n",
4180                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4181                 PieceToChar(PromoPiece(moveType)));
4182         break;
4183       case WhiteDrop:
4184       case BlackDrop:
4185         sprintf(user_move, "%c@%c%c\n",
4186                 ToUpper(PieceToChar((ChessSquare) fromX)),
4187                 AAA + toX, ONE + toY);
4188         break;
4189       case NormalMove:
4190       case WhiteCapturesEnPassant:
4191       case BlackCapturesEnPassant:
4192       case IllegalMove:  /* could be a variant we don't quite understand */
4193         sprintf(user_move, "%c%c%c%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4195         break;
4196     }
4197     SendToICS(user_move);
4198     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4199         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4200 }
4201
4202 void
4203 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4204      int rf, ff, rt, ft;
4205      char promoChar;
4206      char move[7];
4207 {
4208     if (rf == DROP_RANK) {
4209         sprintf(move, "%c@%c%c\n",
4210                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4211     } else {
4212         if (promoChar == 'x' || promoChar == NULLCHAR) {
4213             sprintf(move, "%c%c%c%c\n",
4214                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4215         } else {
4216             sprintf(move, "%c%c%c%c%c\n",
4217                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4218         }
4219     }
4220 }
4221
4222 void
4223 ProcessICSInitScript(f)
4224      FILE *f;
4225 {
4226     char buf[MSG_SIZ];
4227
4228     while (fgets(buf, MSG_SIZ, f)) {
4229         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4230     }
4231
4232     fclose(f);
4233 }
4234
4235
4236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4237 void
4238 AlphaRank(char *move, int n)
4239 {
4240 //    char *p = move, c; int x, y;
4241
4242     if (appData.debugMode) {
4243         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4244     }
4245
4246     if(move[1]=='*' && 
4247        move[2]>='0' && move[2]<='9' &&
4248        move[3]>='a' && move[3]<='x'    ) {
4249         move[1] = '@';
4250         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4251         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4252     } else
4253     if(move[0]>='0' && move[0]<='9' &&
4254        move[1]>='a' && move[1]<='x' &&
4255        move[2]>='0' && move[2]<='9' &&
4256        move[3]>='a' && move[3]<='x'    ) {
4257         /* input move, Shogi -> normal */
4258         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4259         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4260         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4261         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4262     } else
4263     if(move[1]=='@' &&
4264        move[3]>='0' && move[3]<='9' &&
4265        move[2]>='a' && move[2]<='x'    ) {
4266         move[1] = '*';
4267         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4268         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4269     } else
4270     if(
4271        move[0]>='a' && move[0]<='x' &&
4272        move[3]>='0' && move[3]<='9' &&
4273        move[2]>='a' && move[2]<='x'    ) {
4274          /* output move, normal -> Shogi */
4275         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4276         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4277         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4278         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4279         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4280     }
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "   out = '%s'\n", move);
4283     }
4284 }
4285
4286 /* Parser for moves from gnuchess, ICS, or user typein box */
4287 Boolean
4288 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4289      char *move;
4290      int moveNum;
4291      ChessMove *moveType;
4292      int *fromX, *fromY, *toX, *toY;
4293      char *promoChar;
4294 {       
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "move to parse: %s\n", move);
4297     }
4298     *moveType = yylexstr(moveNum, move);
4299
4300     switch (*moveType) {
4301       case WhitePromotionChancellor:
4302       case BlackPromotionChancellor:
4303       case WhitePromotionArchbishop:
4304       case BlackPromotionArchbishop:
4305       case WhitePromotionQueen:
4306       case BlackPromotionQueen:
4307       case WhitePromotionRook:
4308       case BlackPromotionRook:
4309       case WhitePromotionBishop:
4310       case BlackPromotionBishop:
4311       case WhitePromotionKnight:
4312       case BlackPromotionKnight:
4313       case WhitePromotionKing:
4314       case BlackPromotionKing:
4315       case NormalMove:
4316       case WhiteCapturesEnPassant:
4317       case BlackCapturesEnPassant:
4318       case WhiteKingSideCastle:
4319       case WhiteQueenSideCastle:
4320       case BlackKingSideCastle:
4321       case BlackQueenSideCastle:
4322       case WhiteKingSideCastleWild:
4323       case WhiteQueenSideCastleWild:
4324       case BlackKingSideCastleWild:
4325       case BlackQueenSideCastleWild:
4326       /* Code added by Tord: */
4327       case WhiteHSideCastleFR:
4328       case WhiteASideCastleFR:
4329       case BlackHSideCastleFR:
4330       case BlackASideCastleFR:
4331       /* End of code added by Tord */
4332       case IllegalMove:         /* bug or odd chess variant */
4333         *fromX = currentMoveString[0] - AAA;
4334         *fromY = currentMoveString[1] - ONE;
4335         *toX = currentMoveString[2] - AAA;
4336         *toY = currentMoveString[3] - ONE;
4337         *promoChar = currentMoveString[4];
4338         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4339             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4340     if (appData.debugMode) {
4341         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4342     }
4343             *fromX = *fromY = *toX = *toY = 0;
4344             return FALSE;
4345         }
4346         if (appData.testLegality) {
4347           return (*moveType != IllegalMove);
4348         } else {
4349           return !(fromX == fromY && toX == toY);
4350         }
4351
4352       case WhiteDrop:
4353       case BlackDrop:
4354         *fromX = *moveType == WhiteDrop ?
4355           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4356           (int) CharToPiece(ToLower(currentMoveString[0]));
4357         *fromY = DROP_RANK;
4358         *toX = currentMoveString[2] - AAA;
4359         *toY = currentMoveString[3] - ONE;
4360         *promoChar = NULLCHAR;
4361         return TRUE;
4362
4363       case AmbiguousMove:
4364       case ImpossibleMove:
4365       case (ChessMove) 0:       /* end of file */
4366       case ElapsedTime:
4367       case Comment:
4368       case PGNTag:
4369       case NAG:
4370       case WhiteWins:
4371       case BlackWins:
4372       case GameIsDrawn:
4373       default:
4374     if (appData.debugMode) {
4375         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4376     }
4377         /* bug? */
4378         *fromX = *fromY = *toX = *toY = 0;
4379         *promoChar = NULLCHAR;
4380         return FALSE;
4381     }
4382 }
4383
4384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4385 // All positions will have equal probability, but the current method will not provide a unique
4386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4387 #define DARK 1
4388 #define LITE 2
4389 #define ANY 3
4390
4391 int squaresLeft[4];
4392 int piecesLeft[(int)BlackPawn];
4393 int seed, nrOfShuffles;
4394
4395 void GetPositionNumber()
4396 {       // sets global variable seed
4397         int i;
4398
4399         seed = appData.defaultFrcPosition;
4400         if(seed < 0) { // randomize based on time for negative FRC position numbers
4401                 for(i=0; i<50; i++) seed += random();
4402                 seed = random() ^ random() >> 8 ^ random() << 8;
4403                 if(seed<0) seed = -seed;
4404         }
4405 }
4406
4407 int put(Board board, int pieceType, int rank, int n, int shade)
4408 // put the piece on the (n-1)-th empty squares of the given shade
4409 {
4410         int i;
4411
4412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4413                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4414                         board[rank][i] = (ChessSquare) pieceType;
4415                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4416                         squaresLeft[ANY]--;
4417                         piecesLeft[pieceType]--; 
4418                         return i;
4419                 }
4420         }
4421         return -1;
4422 }
4423
4424
4425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4426 // calculate where the next piece goes, (any empty square), and put it there
4427 {
4428         int i;
4429
4430         i = seed % squaresLeft[shade];
4431         nrOfShuffles *= squaresLeft[shade];
4432         seed /= squaresLeft[shade];
4433         put(board, pieceType, rank, i, shade);
4434 }
4435
4436 void AddTwoPieces(Board board, int pieceType, int rank)
4437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4438 {
4439         int i, n=squaresLeft[ANY], j=n-1, k;
4440
4441         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4442         i = seed % k;  // pick one
4443         nrOfShuffles *= k;
4444         seed /= k;
4445         while(i >= j) i -= j--;
4446         j = n - 1 - j; i += j;
4447         put(board, pieceType, rank, j, ANY);
4448         put(board, pieceType, rank, i, ANY);
4449 }
4450
4451 void SetUpShuffle(Board board, int number)
4452 {
4453         int i, p, first=1;
4454
4455         GetPositionNumber(); nrOfShuffles = 1;
4456
4457         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4458         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4459         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4460
4461         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4462
4463         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4464             p = (int) board[0][i];
4465             if(p < (int) BlackPawn) piecesLeft[p] ++;
4466             board[0][i] = EmptySquare;
4467         }
4468
4469         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4470             // shuffles restricted to allow normal castling put KRR first
4471             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4472                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4473             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4474                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4475             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4476                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4477             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4478                 put(board, WhiteRook, 0, 0, ANY);
4479             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4480         }
4481
4482         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4483             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4484             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4485                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4486                 while(piecesLeft[p] >= 2) {
4487                     AddOnePiece(board, p, 0, LITE);
4488                     AddOnePiece(board, p, 0, DARK);
4489                 }
4490                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4491             }
4492
4493         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4494             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4495             // but we leave King and Rooks for last, to possibly obey FRC restriction
4496             if(p == (int)WhiteRook) continue;
4497             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4498             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4499         }
4500
4501         // now everything is placed, except perhaps King (Unicorn) and Rooks
4502
4503         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4504             // Last King gets castling rights
4505             while(piecesLeft[(int)WhiteUnicorn]) {
4506                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4507                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4508             }
4509
4510             while(piecesLeft[(int)WhiteKing]) {
4511                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4512                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4513             }
4514
4515
4516         } else {
4517             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4518             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4519         }
4520
4521         // Only Rooks can be left; simply place them all
4522         while(piecesLeft[(int)WhiteRook]) {
4523                 i = put(board, WhiteRook, 0, 0, ANY);
4524                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4525                         if(first) {
4526                                 first=0;
4527                                 initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4528                         }
4529                         initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4530                 }
4531         }
4532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4533             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4534         }
4535
4536         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4537 }
4538
4539 int SetCharTable( char *table, const char * map )
4540 /* [HGM] moved here from winboard.c because of its general usefulness */
4541 /*       Basically a safe strcpy that uses the last character as King */
4542 {
4543     int result = FALSE; int NrPieces;
4544
4545     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4546                     && NrPieces >= 12 && !(NrPieces&1)) {
4547         int i; /* [HGM] Accept even length from 12 to 34 */
4548
4549         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4550         for( i=0; i<NrPieces/2-1; i++ ) {
4551             table[i] = map[i];
4552             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4553         }
4554         table[(int) WhiteKing]  = map[NrPieces/2-1];
4555         table[(int) BlackKing]  = map[NrPieces-1];
4556
4557         result = TRUE;
4558     }
4559
4560     return result;
4561 }
4562
4563 void Prelude(Board board)
4564 {       // [HGM] superchess: random selection of exo-pieces
4565         int i, j, k; ChessSquare p; 
4566         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4567
4568         GetPositionNumber(); // use FRC position number
4569
4570         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4571             SetCharTable(pieceToChar, appData.pieceToCharTable);
4572             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4573                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4574         }
4575
4576         j = seed%4;                 seed /= 4; 
4577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4580         j = seed%3 + (seed%3 >= j); seed /= 3; 
4581         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4584         j = seed%3;                 seed /= 3; 
4585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4588         j = seed%2 + (seed%2 >= j); seed /= 2; 
4589         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4592         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4593         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4594         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4595         put(board, exoPieces[0],    0, 0, ANY);
4596         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4597 }
4598
4599 void
4600 InitPosition(redraw)
4601      int redraw;
4602 {
4603     ChessSquare (* pieces)[BOARD_FILES];
4604     int i, j, pawnRow, overrule,
4605     oldx = gameInfo.boardWidth,
4606     oldy = gameInfo.boardHeight,
4607     oldh = gameInfo.holdingsWidth,
4608     oldv = gameInfo.variant;
4609
4610     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4611
4612     /* [AS] Initialize pv info list [HGM] and game status */
4613     {
4614         for( i=0; i<MAX_MOVES; i++ ) {
4615             pvInfoList[i].depth = 0;
4616             boards[i][EP_STATUS] = EP_NONE;
4617             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4618         }
4619
4620         initialRulePlies = 0; /* 50-move counter start */
4621
4622         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4623         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4624     }
4625
4626     
4627     /* [HGM] logic here is completely changed. In stead of full positions */
4628     /* the initialized data only consist of the two backranks. The switch */
4629     /* selects which one we will use, which is than copied to the Board   */
4630     /* initialPosition, which for the rest is initialized by Pawns and    */
4631     /* empty squares. This initial position is then copied to boards[0],  */
4632     /* possibly after shuffling, so that it remains available.            */
4633
4634     gameInfo.holdingsWidth = 0; /* default board sizes */
4635     gameInfo.boardWidth    = 8;
4636     gameInfo.boardHeight   = 8;
4637     gameInfo.holdingsSize  = 0;
4638     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4639     for(i=0; i<BOARD_FILES-2; i++)
4640       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4641     initialPosition[EP_STATUS] = EP_NONE;
4642     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4643
4644     switch (gameInfo.variant) {
4645     case VariantFischeRandom:
4646       shuffleOpenings = TRUE;
4647     default:
4648       pieces = FIDEArray;
4649       break;
4650     case VariantShatranj:
4651       pieces = ShatranjArray;
4652       nrCastlingRights = 0;
4653       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4654       break;
4655     case VariantTwoKings:
4656       pieces = twoKingsArray;
4657       break;
4658     case VariantCapaRandom:
4659       shuffleOpenings = TRUE;
4660     case VariantCapablanca:
4661       pieces = CapablancaArray;
4662       gameInfo.boardWidth = 10;
4663       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4664       break;
4665     case VariantGothic:
4666       pieces = GothicArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4669       break;
4670     case VariantJanus:
4671       pieces = JanusArray;
4672       gameInfo.boardWidth = 10;
4673       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4674       nrCastlingRights = 6;
4675         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4676         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4677         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4678         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4679         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4680         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4681       break;
4682     case VariantFalcon:
4683       pieces = FalconArray;
4684       gameInfo.boardWidth = 10;
4685       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4686       break;
4687     case VariantXiangqi:
4688       pieces = XiangqiArray;
4689       gameInfo.boardWidth  = 9;
4690       gameInfo.boardHeight = 10;
4691       nrCastlingRights = 0;
4692       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4693       break;
4694     case VariantShogi:
4695       pieces = ShogiArray;
4696       gameInfo.boardWidth  = 9;
4697       gameInfo.boardHeight = 9;
4698       gameInfo.holdingsSize = 7;
4699       nrCastlingRights = 0;
4700       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4701       break;
4702     case VariantCourier:
4703       pieces = CourierArray;
4704       gameInfo.boardWidth  = 12;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4707       break;
4708     case VariantKnightmate:
4709       pieces = KnightmateArray;
4710       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4711       break;
4712     case VariantFairy:
4713       pieces = fairyArray;
4714       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4715       break;
4716     case VariantGreat:
4717       pieces = GreatArray;
4718       gameInfo.boardWidth = 10;
4719       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4720       gameInfo.holdingsSize = 8;
4721       break;
4722     case VariantSuper:
4723       pieces = FIDEArray;
4724       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4725       gameInfo.holdingsSize = 8;
4726       startedFromSetupPosition = TRUE;
4727       break;
4728     case VariantCrazyhouse:
4729     case VariantBughouse:
4730       pieces = FIDEArray;
4731       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4732       gameInfo.holdingsSize = 5;
4733       break;
4734     case VariantWildCastle:
4735       pieces = FIDEArray;
4736       /* !!?shuffle with kings guaranteed to be on d or e file */
4737       shuffleOpenings = 1;
4738       break;
4739     case VariantNoCastle:
4740       pieces = FIDEArray;
4741       nrCastlingRights = 0;
4742       /* !!?unconstrained back-rank shuffle */
4743       shuffleOpenings = 1;
4744       break;
4745     }
4746
4747     overrule = 0;
4748     if(appData.NrFiles >= 0) {
4749         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4750         gameInfo.boardWidth = appData.NrFiles;
4751     }
4752     if(appData.NrRanks >= 0) {
4753         gameInfo.boardHeight = appData.NrRanks;
4754     }
4755     if(appData.holdingsSize >= 0) {
4756         i = appData.holdingsSize;
4757         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4758         gameInfo.holdingsSize = i;
4759     }
4760     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4761     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4762         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4763
4764     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4765     if(pawnRow < 1) pawnRow = 1;
4766
4767     /* User pieceToChar list overrules defaults */
4768     if(appData.pieceToCharTable != NULL)
4769         SetCharTable(pieceToChar, appData.pieceToCharTable);
4770
4771     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4772
4773         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4774             s = (ChessSquare) 0; /* account holding counts in guard band */
4775         for( i=0; i<BOARD_HEIGHT; i++ )
4776             initialPosition[i][j] = s;
4777
4778         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4779         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4780         initialPosition[pawnRow][j] = WhitePawn;
4781         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4782         if(gameInfo.variant == VariantXiangqi) {
4783             if(j&1) {
4784                 initialPosition[pawnRow][j] = 
4785                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4786                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4787                    initialPosition[2][j] = WhiteCannon;
4788                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4789                 }
4790             }
4791         }
4792         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4793     }
4794     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4795
4796             j=BOARD_LEFT+1;
4797             initialPosition[1][j] = WhiteBishop;
4798             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4799             j=BOARD_RGHT-2;
4800             initialPosition[1][j] = WhiteRook;
4801             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4802     }
4803
4804     if( nrCastlingRights == -1) {
4805         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4806         /*       This sets default castling rights from none to normal corners   */
4807         /* Variants with other castling rights must set them themselves above    */
4808         nrCastlingRights = 6;
4809        
4810         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4811         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4812         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4813         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4814         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4815         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4816      }
4817
4818      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4819      if(gameInfo.variant == VariantGreat) { // promotion commoners
4820         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4821         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4822         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4823         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4824      }
4825   if (appData.debugMode) {
4826     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4827   }
4828     if(shuffleOpenings) {
4829         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4830         startedFromSetupPosition = TRUE;
4831     }
4832     if(startedFromPositionFile) {
4833       /* [HGM] loadPos: use PositionFile for every new game */
4834       CopyBoard(initialPosition, filePosition);
4835       for(i=0; i<nrCastlingRights; i++)
4836           initialRights[i] = filePosition[CASTLING][i];
4837       startedFromSetupPosition = TRUE;
4838     }
4839
4840     CopyBoard(boards[0], initialPosition);
4841
4842     if(oldx != gameInfo.boardWidth ||
4843        oldy != gameInfo.boardHeight ||
4844        oldh != gameInfo.holdingsWidth
4845 #ifdef GOTHIC
4846        || oldv == VariantGothic ||        // For licensing popups
4847        gameInfo.variant == VariantGothic
4848 #endif
4849 #ifdef FALCON
4850        || oldv == VariantFalcon ||
4851        gameInfo.variant == VariantFalcon
4852 #endif
4853                                          )
4854             InitDrawingSizes(-2 ,0);
4855
4856     if (redraw)
4857       DrawPosition(TRUE, boards[currentMove]);
4858 }
4859
4860 void
4861 SendBoard(cps, moveNum)
4862      ChessProgramState *cps;
4863      int moveNum;
4864 {
4865     char message[MSG_SIZ];
4866     
4867     if (cps->useSetboard) {
4868       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4869       sprintf(message, "setboard %s\n", fen);
4870       SendToProgram(message, cps);
4871       free(fen);
4872
4873     } else {
4874       ChessSquare *bp;
4875       int i, j;
4876       /* Kludge to set black to move, avoiding the troublesome and now
4877        * deprecated "black" command.
4878        */
4879       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4880
4881       SendToProgram("edit\n", cps);
4882       SendToProgram("#\n", cps);
4883       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4884         bp = &boards[moveNum][i][BOARD_LEFT];
4885         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4886           if ((int) *bp < (int) BlackPawn) {
4887             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4888                     AAA + j, ONE + i);
4889             if(message[0] == '+' || message[0] == '~') {
4890                 sprintf(message, "%c%c%c+\n",
4891                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4892                         AAA + j, ONE + i);
4893             }
4894             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4895                 message[1] = BOARD_RGHT   - 1 - j + '1';
4896                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4897             }
4898             SendToProgram(message, cps);
4899           }
4900         }
4901       }
4902     
4903       SendToProgram("c\n", cps);
4904       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4905         bp = &boards[moveNum][i][BOARD_LEFT];
4906         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4907           if (((int) *bp != (int) EmptySquare)
4908               && ((int) *bp >= (int) BlackPawn)) {
4909             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4910                     AAA + j, ONE + i);
4911             if(message[0] == '+' || message[0] == '~') {
4912                 sprintf(message, "%c%c%c+\n",
4913                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4914                         AAA + j, ONE + i);
4915             }
4916             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4917                 message[1] = BOARD_RGHT   - 1 - j + '1';
4918                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4919             }
4920             SendToProgram(message, cps);
4921           }
4922         }
4923       }
4924     
4925       SendToProgram(".\n", cps);
4926     }
4927     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4928 }
4929
4930 int
4931 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4932 {
4933     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4934     /* [HGM] add Shogi promotions */
4935     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4936     ChessSquare piece;
4937     ChessMove moveType;
4938     Boolean premove;
4939
4940     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4941     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4942
4943     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4944       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4945         return FALSE;
4946
4947     piece = boards[currentMove][fromY][fromX];
4948     if(gameInfo.variant == VariantShogi) {
4949         promotionZoneSize = 3;
4950         highestPromotingPiece = (int)WhiteFerz;
4951     }
4952
4953     // next weed out all moves that do not touch the promotion zone at all
4954     if((int)piece >= BlackPawn) {
4955         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4956              return FALSE;
4957         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4958     } else {
4959         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4960            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4961     }
4962
4963     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4964
4965     // weed out mandatory Shogi promotions
4966     if(gameInfo.variant == VariantShogi) {
4967         if(piece >= BlackPawn) {
4968             if(toY == 0 && piece == BlackPawn ||
4969                toY == 0 && piece == BlackQueen ||
4970                toY <= 1 && piece == BlackKnight) {
4971                 *promoChoice = '+';
4972                 return FALSE;
4973             }
4974         } else {
4975             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4976                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4977                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4978                 *promoChoice = '+';
4979                 return FALSE;
4980             }
4981         }
4982     }
4983
4984     // weed out obviously illegal Pawn moves
4985     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4986         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4987         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4988         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4989         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4990         // note we are not allowed to test for valid (non-)capture, due to premove
4991     }
4992
4993     // we either have a choice what to promote to, or (in Shogi) whether to promote
4994     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4995         *promoChoice = PieceToChar(BlackFerz);  // no choice
4996         return FALSE;
4997     }
4998     if(appData.alwaysPromoteToQueen) { // predetermined
4999         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5000              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5001         else *promoChoice = PieceToChar(BlackQueen);
5002         return FALSE;
5003     }
5004
5005     // suppress promotion popup on illegal moves that are not premoves
5006     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5007               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5008     if(appData.testLegality && !premove) {
5009         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5010                         fromY, fromX, toY, toX, NULLCHAR);
5011         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5012            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5013             return FALSE;
5014     }
5015
5016     return TRUE;
5017 }
5018
5019 int
5020 InPalace(row, column)
5021      int row, column;
5022 {   /* [HGM] for Xiangqi */
5023     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5024          column < (BOARD_WIDTH + 4)/2 &&
5025          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5026     return FALSE;
5027 }
5028
5029 int
5030 PieceForSquare (x, y)
5031      int x;
5032      int y;
5033 {
5034   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5035      return -1;
5036   else
5037      return boards[currentMove][y][x];
5038 }
5039
5040 int
5041 OKToStartUserMove(x, y)
5042      int x, y;
5043 {
5044     ChessSquare from_piece;
5045     int white_piece;
5046
5047     if (matchMode) return FALSE;
5048     if (gameMode == EditPosition) return TRUE;
5049
5050     if (x >= 0 && y >= 0)
5051       from_piece = boards[currentMove][y][x];
5052     else
5053       from_piece = EmptySquare;
5054
5055     if (from_piece == EmptySquare) return FALSE;
5056
5057     white_piece = (int)from_piece >= (int)WhitePawn &&
5058       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5059
5060     switch (gameMode) {
5061       case PlayFromGameFile:
5062       case AnalyzeFile:
5063       case TwoMachinesPlay:
5064       case EndOfGame:
5065         return FALSE;
5066
5067       case IcsObserving:
5068       case IcsIdle:
5069         return FALSE;
5070
5071       case MachinePlaysWhite:
5072       case IcsPlayingBlack:
5073         if (appData.zippyPlay) return FALSE;
5074         if (white_piece) {
5075             DisplayMoveError(_("You are playing Black"));
5076             return FALSE;
5077         }
5078         break;
5079
5080       case MachinePlaysBlack:
5081       case IcsPlayingWhite:
5082         if (appData.zippyPlay) return FALSE;
5083         if (!white_piece) {
5084             DisplayMoveError(_("You are playing White"));
5085             return FALSE;
5086         }
5087         break;
5088
5089       case EditGame:
5090         if (!white_piece && WhiteOnMove(currentMove)) {
5091             DisplayMoveError(_("It is White's turn"));
5092             return FALSE;
5093         }           
5094         if (white_piece && !WhiteOnMove(currentMove)) {
5095             DisplayMoveError(_("It is Black's turn"));
5096             return FALSE;
5097         }           
5098         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5099             /* Editing correspondence game history */
5100             /* Could disallow this or prompt for confirmation */
5101             cmailOldMove = -1;
5102         }
5103         if (currentMove < forwardMostMove) {
5104             /* Discarding moves */
5105             /* Could prompt for confirmation here,
5106                but I don't think that's such a good idea */
5107             forwardMostMove = currentMove;
5108         }
5109         break;
5110
5111       case BeginningOfGame:
5112         if (appData.icsActive) return FALSE;
5113         if (!appData.noChessProgram) {
5114             if (!white_piece) {
5115                 DisplayMoveError(_("You are playing White"));
5116                 return FALSE;
5117             }
5118         }
5119         break;
5120         
5121       case Training:
5122         if (!white_piece && WhiteOnMove(currentMove)) {
5123             DisplayMoveError(_("It is White's turn"));
5124             return FALSE;
5125         }           
5126         if (white_piece && !WhiteOnMove(currentMove)) {
5127             DisplayMoveError(_("It is Black's turn"));
5128             return FALSE;
5129         }           
5130         break;
5131
5132       default:
5133       case IcsExamining:
5134         break;
5135     }
5136     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5137         && gameMode != AnalyzeFile && gameMode != Training) {
5138         DisplayMoveError(_("Displayed position is not current"));
5139         return FALSE;
5140     }
5141     return TRUE;
5142 }
5143
5144 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5145 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5146 int lastLoadGameUseList = FALSE;
5147 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5148 ChessMove lastLoadGameStart = (ChessMove) 0;
5149
5150 ChessMove
5151 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5152      int fromX, fromY, toX, toY;
5153      int promoChar;
5154      Boolean captureOwn;
5155 {
5156     ChessMove moveType;
5157     ChessSquare pdown, pup;
5158
5159     /* Check if the user is playing in turn.  This is complicated because we
5160        let the user "pick up" a piece before it is his turn.  So the piece he
5161        tried to pick up may have been captured by the time he puts it down!
5162        Therefore we use the color the user is supposed to be playing in this
5163        test, not the color of the piece that is currently on the starting
5164        square---except in EditGame mode, where the user is playing both
5165        sides; fortunately there the capture race can't happen.  (It can
5166        now happen in IcsExamining mode, but that's just too bad.  The user
5167        will get a somewhat confusing message in that case.)
5168        */
5169
5170     switch (gameMode) {
5171       case PlayFromGameFile:
5172       case AnalyzeFile:
5173       case TwoMachinesPlay:
5174       case EndOfGame:
5175       case IcsObserving:
5176       case IcsIdle:
5177         /* We switched into a game mode where moves are not accepted,
5178            perhaps while the mouse button was down. */
5179         return ImpossibleMove;
5180
5181       case MachinePlaysWhite:
5182         /* User is moving for Black */
5183         if (WhiteOnMove(currentMove)) {
5184             DisplayMoveError(_("It is White's turn"));
5185             return ImpossibleMove;
5186         }
5187         break;
5188
5189       case MachinePlaysBlack:
5190         /* User is moving for White */
5191         if (!WhiteOnMove(currentMove)) {
5192             DisplayMoveError(_("It is Black's turn"));
5193             return ImpossibleMove;
5194         }
5195         break;
5196
5197       case EditGame:
5198       case IcsExamining:
5199       case BeginningOfGame:
5200       case AnalyzeMode:
5201       case Training:
5202         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5203             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5204             /* User is moving for Black */
5205             if (WhiteOnMove(currentMove)) {
5206                 DisplayMoveError(_("It is White's turn"));
5207                 return ImpossibleMove;
5208             }
5209         } else {
5210             /* User is moving for White */
5211             if (!WhiteOnMove(currentMove)) {
5212                 DisplayMoveError(_("It is Black's turn"));
5213                 return ImpossibleMove;
5214             }
5215         }
5216         break;
5217
5218       case IcsPlayingBlack:
5219         /* User is moving for Black */
5220         if (WhiteOnMove(currentMove)) {
5221             if (!appData.premove) {
5222                 DisplayMoveError(_("It is White's turn"));
5223             } else if (toX >= 0 && toY >= 0) {
5224                 premoveToX = toX;
5225                 premoveToY = toY;
5226                 premoveFromX = fromX;
5227                 premoveFromY = fromY;
5228                 premovePromoChar = promoChar;
5229                 gotPremove = 1;
5230                 if (appData.debugMode) 
5231                     fprintf(debugFP, "Got premove: fromX %d,"
5232                             "fromY %d, toX %d, toY %d\n",
5233                             fromX, fromY, toX, toY);
5234             }
5235             return ImpossibleMove;
5236         }
5237         break;
5238
5239       case IcsPlayingWhite:
5240         /* User is moving for White */
5241         if (!WhiteOnMove(currentMove)) {
5242             if (!appData.premove) {
5243                 DisplayMoveError(_("It is Black's turn"));
5244             } else if (toX >= 0 && toY >= 0) {
5245                 premoveToX = toX;
5246                 premoveToY = toY;
5247                 premoveFromX = fromX;
5248                 premoveFromY = fromY;
5249                 premovePromoChar = promoChar;
5250                 gotPremove = 1;
5251                 if (appData.debugMode) 
5252                     fprintf(debugFP, "Got premove: fromX %d,"
5253                             "fromY %d, toX %d, toY %d\n",
5254                             fromX, fromY, toX, toY);
5255             }
5256             return ImpossibleMove;
5257         }
5258         break;
5259
5260       default:
5261         break;
5262
5263       case EditPosition:
5264         /* EditPosition, empty square, or different color piece;
5265            click-click move is possible */
5266         if (toX == -2 || toY == -2) {
5267             boards[0][fromY][fromX] = EmptySquare;
5268             return AmbiguousMove;
5269         } else if (toX >= 0 && toY >= 0) {
5270             boards[0][toY][toX] = boards[0][fromY][fromX];
5271             boards[0][fromY][fromX] = EmptySquare;
5272             return AmbiguousMove;
5273         }
5274         return ImpossibleMove;
5275     }
5276
5277     if(toX < 0 || toY < 0) return ImpossibleMove;
5278     pdown = boards[currentMove][fromY][fromX];
5279     pup = boards[currentMove][toY][toX];
5280
5281     /* [HGM] If move started in holdings, it means a drop */
5282     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5283          if( pup != EmptySquare ) return ImpossibleMove;
5284          if(appData.testLegality) {
5285              /* it would be more logical if LegalityTest() also figured out
5286               * which drops are legal. For now we forbid pawns on back rank.
5287               * Shogi is on its own here...
5288               */
5289              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5290                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5291                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5292          }
5293          return WhiteDrop; /* Not needed to specify white or black yet */
5294     }
5295
5296     userOfferedDraw = FALSE;
5297         
5298     /* [HGM] always test for legality, to get promotion info */
5299     moveType = LegalityTest(boards[currentMove], PosFlags(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;
5367       CopyBoard(testBoard, boards[currentMove]);
5368       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
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     case MT_NONE:
5461     case MT_CHECK:
5462       break;
5463     case MT_CHECKMATE:
5464     case MT_STAINMATE:
5465       if (WhiteOnMove(currentMove)) {
5466         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5467       } else {
5468         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5469       }
5470       break;
5471     case MT_STALEMATE:
5472       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5473       break;
5474     }
5475     break;
5476     
5477   case MachinePlaysBlack:
5478   case MachinePlaysWhite:
5479     /* disable certain menu options while machine is thinking */
5480     SetMachineThinkingEnables();
5481     break;
5482
5483   default:
5484     break;
5485   }
5486
5487   if(bookHit) { // [HGM] book: simulate book reply
5488         static char bookMove[MSG_SIZ]; // a bit generous?
5489
5490         programStats.nodes = programStats.depth = programStats.time = 
5491         programStats.score = programStats.got_only_move = 0;
5492         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5493
5494         strcpy(bookMove, "move ");
5495         strcat(bookMove, bookHit);
5496         HandleMachineMove(bookMove, &first);
5497   }
5498   return 1;
5499 }
5500
5501 void
5502 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5503      int fromX, fromY, toX, toY;
5504      int promoChar;
5505 {
5506     /* [HGM] This routine was added to allow calling of its two logical
5507        parts from other modules in the old way. Before, UserMoveEvent()
5508        automatically called FinishMove() if the move was OK, and returned
5509        otherwise. I separated the two, in order to make it possible to
5510        slip a promotion popup in between. But that it always needs two
5511        calls, to the first part, (now called UserMoveTest() ), and to
5512        FinishMove if the first part succeeded. Calls that do not need
5513        to do anything in between, can call this routine the old way. 
5514     */
5515     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5516 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5517     if(moveType == AmbiguousMove)
5518         DrawPosition(FALSE, boards[currentMove]);
5519     else if(moveType != ImpossibleMove && moveType != Comment)
5520         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5521 }
5522
5523 void LeftClick(ClickType clickType, int xPix, int yPix)
5524 {
5525     int x, y;
5526     Boolean saveAnimate;
5527     static int second = 0, promotionChoice = 0;
5528     char promoChoice = NULLCHAR;
5529
5530     if (clickType == Press) ErrorPopDown();
5531
5532     x = EventToSquare(xPix, BOARD_WIDTH);
5533     y = EventToSquare(yPix, BOARD_HEIGHT);
5534     if (!flipView && y >= 0) {
5535         y = BOARD_HEIGHT - 1 - y;
5536     }
5537     if (flipView && x >= 0) {
5538         x = BOARD_WIDTH - 1 - x;
5539     }
5540
5541     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5542         if(clickType == Release) return; // ignore upclick of click-click destination
5543         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5544         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5545         if(gameInfo.holdingsWidth && 
5546                 (WhiteOnMove(currentMove) 
5547                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5548                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5549             // click in right holdings, for determining promotion piece
5550             ChessSquare p = boards[currentMove][y][x];
5551             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5552             if(p != EmptySquare) {
5553                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5554                 fromX = fromY = -1;
5555                 return;
5556             }
5557         }
5558         DrawPosition(FALSE, boards[currentMove]);
5559         return;
5560     }
5561
5562     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5563     if(clickType == Press
5564             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5565               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5566               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5567         return;
5568
5569     if (fromX == -1) {
5570         if (clickType == Press) {
5571             /* First square */
5572             if (OKToStartUserMove(x, y)) {
5573                 fromX = x;
5574                 fromY = y;
5575                 second = 0;
5576                 DragPieceBegin(xPix, yPix);
5577                 if (appData.highlightDragging) {
5578                     SetHighlights(x, y, -1, -1);
5579                 }
5580             }
5581         }
5582         return;
5583     }
5584
5585     /* fromX != -1 */
5586     if (clickType == Press && gameMode != EditPosition) {
5587         ChessSquare fromP;
5588         ChessSquare toP;
5589         int frc;
5590
5591         // ignore off-board to clicks
5592         if(y < 0 || x < 0) return;
5593
5594         /* Check if clicking again on the same color piece */
5595         fromP = boards[currentMove][fromY][fromX];
5596         toP = boards[currentMove][y][x];
5597         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5598         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5599              WhitePawn <= toP && toP <= WhiteKing &&
5600              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5601              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5602             (BlackPawn <= fromP && fromP <= BlackKing && 
5603              BlackPawn <= toP && toP <= BlackKing &&
5604              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5605              !(fromP == BlackKing && toP == BlackRook && frc))) {
5606             /* Clicked again on same color piece -- changed his mind */
5607             second = (x == fromX && y == fromY);
5608             if (appData.highlightDragging) {
5609                 SetHighlights(x, y, -1, -1);
5610             } else {
5611                 ClearHighlights();
5612             }
5613             if (OKToStartUserMove(x, y)) {
5614                 fromX = x;
5615                 fromY = y;
5616                 DragPieceBegin(xPix, yPix);
5617             }
5618             return;
5619         }
5620         // ignore clicks on holdings
5621         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5622     }
5623
5624     if (clickType == Release && x == fromX && y == fromY) {
5625         DragPieceEnd(xPix, yPix);
5626         if (appData.animateDragging) {
5627             /* Undo animation damage if any */
5628             DrawPosition(FALSE, NULL);
5629         }
5630         if (second) {
5631             /* Second up/down in same square; just abort move */
5632             second = 0;
5633             fromX = fromY = -1;
5634             ClearHighlights();
5635             gotPremove = 0;
5636             ClearPremoveHighlights();
5637         } else {
5638             /* First upclick in same square; start click-click mode */
5639             SetHighlights(x, y, -1, -1);
5640         }
5641         return;
5642     }
5643
5644     /* we now have a different from- and (possibly off-board) to-square */
5645     /* Completed move */
5646     toX = x;
5647     toY = y;
5648     saveAnimate = appData.animate;
5649     if (clickType == Press) {
5650         /* Finish clickclick move */
5651         if (appData.animate || appData.highlightLastMove) {
5652             SetHighlights(fromX, fromY, toX, toY);
5653         } else {
5654             ClearHighlights();
5655         }
5656     } else {
5657         /* Finish drag move */
5658         if (appData.highlightLastMove) {
5659             SetHighlights(fromX, fromY, toX, toY);
5660         } else {
5661             ClearHighlights();
5662         }
5663         DragPieceEnd(xPix, yPix);
5664         /* Don't animate move and drag both */
5665         appData.animate = FALSE;
5666     }
5667
5668     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5669     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5670         ClearHighlights();
5671         fromX = fromY = -1;
5672         DrawPosition(TRUE, NULL);
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                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5896                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5897     }
5898         if(cps->alphaRank) AlphaRank(machineMove, 4);
5899         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5900                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5901             /* Machine move could not be parsed; ignore it. */
5902             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5903                     machineMove, cps->which);
5904             DisplayError(buf1, 0);
5905             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5906                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5907             if (gameMode == TwoMachinesPlay) {
5908               GameEnds(machineWhite ? BlackWins : WhiteWins,
5909                        buf1, GE_XBOARD);
5910             }
5911             return;
5912         }
5913
5914         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5915         /* So we have to redo legality test with true e.p. status here,  */
5916         /* to make sure an illegal e.p. capture does not slip through,   */
5917         /* to cause a forfeit on a justified illegal-move complaint      */
5918         /* of the opponent.                                              */
5919         if( gameMode==TwoMachinesPlay && appData.testLegality
5920             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5921                                                               ) {
5922            ChessMove moveType;
5923            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5924                              fromY, fromX, toY, toX, promoChar);
5925             if (appData.debugMode) {
5926                 int i;
5927                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5928                     boards[forwardMostMove][CASTLING][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; 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                      boards[forwardMostMove][EP_STATUS] = 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                      boards[forwardMostMove][EP_STATUS] = 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                         boards[forwardMostMove][EP_STATUS] = 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)) ) {
6129               case MT_CHECK:
6130                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6131                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6132                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6133                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6134                             checkCnt++;
6135                         if(checkCnt >= 2) {
6136                             reason = "Xboard adjudication: 3rd check";
6137                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6138                             break;
6139                         }
6140                     }
6141                 }
6142               case MT_NONE:
6143               default:
6144                 break;
6145               case MT_STALEMATE:
6146               case MT_STAINMATE:
6147                 reason = "Xboard adjudication: Stalemate";
6148                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6149                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6150                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6151                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6152                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6153                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6154                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6155                                                                         EP_CHECKMATE : EP_WINS);
6156                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6157                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6158                 }
6159                 break;
6160               case MT_CHECKMATE:
6161                 reason = "Xboard adjudication: Checkmate";
6162                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6163                 break;
6164             }
6165
6166                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6167                     case EP_STALEMATE:
6168                         result = GameIsDrawn; break;
6169                     case EP_CHECKMATE:
6170                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6171                     case EP_WINS:
6172                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6173                     default:
6174                         result = (ChessMove) 0;
6175                 }
6176                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6177                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6178                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6179                     GameEnds( result, reason, GE_XBOARD );
6180                     return;
6181                 }
6182
6183                 /* Next absolutely insufficient mating material. */
6184                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6185                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6186                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6187                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6188                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6189
6190                      /* always flag draws, for judging claims */
6191                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6192
6193                      if(appData.materialDraws) {
6194                          /* but only adjudicate them if adjudication enabled */
6195                          SendToProgram("force\n", cps->other); // suppress reply
6196                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6197                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6198                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6199                          return;
6200                      }
6201                 }
6202
6203                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6204                 if(NrPieces == 4 && 
6205                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6206                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6207                    || NrWN==2 || NrBN==2     /* KNNK */
6208                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6209                   ) ) {
6210                      if(--moveCount < 0 && appData.trivialDraws)
6211                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6212                           SendToProgram("force\n", cps->other); // suppress reply
6213                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6214                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6215                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6216                           return;
6217                      }
6218                 } else moveCount = 6;
6219             }
6220           }
6221           
6222           if (appData.debugMode) { int i;
6223             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6224                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6225                     appData.drawRepeats);
6226             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6227               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6228             
6229           }
6230
6231                 /* Check for rep-draws */
6232                 count = 0;
6233                 for(k = forwardMostMove-2;
6234                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6235                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6236                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6237                     k-=2)
6238                 {   int rights=0;
6239                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6240                         /* compare castling rights */
6241                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6242                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6243                                 rights++; /* King lost rights, while rook still had them */
6244                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6245                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6246                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6247                                    rights++; /* but at least one rook lost them */
6248                         }
6249                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6250                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6251                                 rights++; 
6252                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6253                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6254                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6255                                    rights++;
6256                         }
6257                         if( rights == 0 && ++count > appData.drawRepeats-2
6258                             && appData.drawRepeats > 1) {
6259                              /* adjudicate after user-specified nr of repeats */
6260                              SendToProgram("force\n", cps->other); // suppress reply
6261                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6262                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6263                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6264                                 // [HGM] xiangqi: check for forbidden perpetuals
6265                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6266                                 for(m=forwardMostMove; m>k; m-=2) {
6267                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6268                                         ourPerpetual = 0; // the current mover did not always check
6269                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6270                                         hisPerpetual = 0; // the opponent did not always check
6271                                 }
6272                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6273                                                                         ourPerpetual, hisPerpetual);
6274                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6275                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6276                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6277                                     return;
6278                                 }
6279                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6280                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6281                                 // Now check for perpetual chases
6282                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6283                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6284                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6285                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6286                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6287                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6288                                         return;
6289                                     }
6290                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6291                                         break; // Abort repetition-checking loop.
6292                                 }
6293                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6294                              }
6295                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6296                              return;
6297                         }
6298                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6299                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6300                     }
6301                 }
6302
6303                 /* Now we test for 50-move draws. Determine ply count */
6304                 count = forwardMostMove;
6305                 /* look for last irreversble move */
6306                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6307                     count--;
6308                 /* if we hit starting position, add initial plies */
6309                 if( count == backwardMostMove )
6310                     count -= initialRulePlies;
6311                 count = forwardMostMove - count; 
6312                 if( count >= 100)
6313                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6314                          /* this is used to judge if draw claims are legal */
6315                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6316                          SendToProgram("force\n", cps->other); // suppress reply
6317                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6318                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6319                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6320                          return;
6321                 }
6322
6323                 /* if draw offer is pending, treat it as a draw claim
6324                  * when draw condition present, to allow engines a way to
6325                  * claim draws before making their move to avoid a race
6326                  * condition occurring after their move
6327                  */
6328                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6329                          char *p = NULL;
6330                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6331                              p = "Draw claim: 50-move rule";
6332                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6333                              p = "Draw claim: 3-fold repetition";
6334                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6335                              p = "Draw claim: insufficient mating material";
6336                          if( p != NULL ) {
6337                              SendToProgram("force\n", cps->other); // suppress reply
6338                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6339                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6340                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6341                              return;
6342                          }
6343                 }
6344
6345
6346                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6347                     SendToProgram("force\n", cps->other); // suppress reply
6348                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350
6351                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6352
6353                     return;
6354                 }
6355         }
6356
6357         bookHit = NULL;
6358         if (gameMode == TwoMachinesPlay) {
6359             /* [HGM] relaying draw offers moved to after reception of move */
6360             /* and interpreting offer as claim if it brings draw condition */
6361             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6362                 SendToProgram("draw\n", cps->other);
6363             }
6364             if (cps->other->sendTime) {
6365                 SendTimeRemaining(cps->other,
6366                                   cps->other->twoMachinesColor[0] == 'w');
6367             }
6368             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6369             if (firstMove && !bookHit) {
6370                 firstMove = FALSE;
6371                 if (cps->other->useColors) {
6372                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6373                 }
6374                 SendToProgram("go\n", cps->other);
6375             }
6376             cps->other->maybeThinking = TRUE;
6377         }
6378
6379         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6380         
6381         if (!pausing && appData.ringBellAfterMoves) {
6382             RingBell();
6383         }
6384
6385         /* 
6386          * Reenable menu items that were disabled while
6387          * machine was thinking
6388          */
6389         if (gameMode != TwoMachinesPlay)
6390             SetUserThinkingEnables();
6391
6392         // [HGM] book: after book hit opponent has received move and is now in force mode
6393         // force the book reply into it, and then fake that it outputted this move by jumping
6394         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6395         if(bookHit) {
6396                 static char bookMove[MSG_SIZ]; // a bit generous?
6397
6398                 strcpy(bookMove, "move ");
6399                 strcat(bookMove, bookHit);
6400                 message = bookMove;
6401                 cps = cps->other;
6402                 programStats.nodes = programStats.depth = programStats.time = 
6403                 programStats.score = programStats.got_only_move = 0;
6404                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6405
6406                 if(cps->lastPing != cps->lastPong) {
6407                     savedMessage = message; // args for deferred call
6408                     savedState = cps;
6409                     ScheduleDelayedEvent(DeferredBookMove, 10);
6410                     return;
6411                 }
6412                 goto FakeBookMove;
6413         }
6414
6415         return;
6416     }
6417
6418     /* Set special modes for chess engines.  Later something general
6419      *  could be added here; for now there is just one kludge feature,
6420      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6421      *  when "xboard" is given as an interactive command.
6422      */
6423     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6424         cps->useSigint = FALSE;
6425         cps->useSigterm = FALSE;
6426     }
6427     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6428       ParseFeatures(message+8, cps);
6429       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6430     }
6431
6432     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6433      * want this, I was asked to put it in, and obliged.
6434      */
6435     if (!strncmp(message, "setboard ", 9)) {
6436         Board initial_position;
6437
6438         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6439
6440         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6441             DisplayError(_("Bad FEN received from engine"), 0);
6442             return ;
6443         } else {
6444            Reset(TRUE, FALSE);
6445            CopyBoard(boards[0], initial_position);
6446            initialRulePlies = FENrulePlies;
6447            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6448            else gameMode = MachinePlaysBlack;                 
6449            DrawPosition(FALSE, boards[currentMove]);
6450         }
6451         return;
6452     }
6453
6454     /*
6455      * Look for communication commands
6456      */
6457     if (!strncmp(message, "telluser ", 9)) {
6458         DisplayNote(message + 9);
6459         return;
6460     }
6461     if (!strncmp(message, "tellusererror ", 14)) {
6462         DisplayError(message + 14, 0);
6463         return;
6464     }
6465     if (!strncmp(message, "tellopponent ", 13)) {
6466       if (appData.icsActive) {
6467         if (loggedOn) {
6468           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6469           SendToICS(buf1);
6470         }
6471       } else {
6472         DisplayNote(message + 13);
6473       }
6474       return;
6475     }
6476     if (!strncmp(message, "tellothers ", 11)) {
6477       if (appData.icsActive) {
6478         if (loggedOn) {
6479           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6480           SendToICS(buf1);
6481         }
6482       }
6483       return;
6484     }
6485     if (!strncmp(message, "tellall ", 8)) {
6486       if (appData.icsActive) {
6487         if (loggedOn) {
6488           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6489           SendToICS(buf1);
6490         }
6491       } else {
6492         DisplayNote(message + 8);
6493       }
6494       return;
6495     }
6496     if (strncmp(message, "warning", 7) == 0) {
6497         /* Undocumented feature, use tellusererror in new code */
6498         DisplayError(message, 0);
6499         return;
6500     }
6501     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6502         strcpy(realname, cps->tidy);
6503         strcat(realname, " query");
6504         AskQuestion(realname, buf2, buf1, cps->pr);
6505         return;
6506     }
6507     /* Commands from the engine directly to ICS.  We don't allow these to be 
6508      *  sent until we are logged on. Crafty kibitzes have been known to 
6509      *  interfere with the login process.
6510      */
6511     if (loggedOn) {
6512         if (!strncmp(message, "tellics ", 8)) {
6513             SendToICS(message + 8);
6514             SendToICS("\n");
6515             return;
6516         }
6517         if (!strncmp(message, "tellicsnoalias ", 15)) {
6518             SendToICS(ics_prefix);
6519             SendToICS(message + 15);
6520             SendToICS("\n");
6521             return;
6522         }
6523         /* The following are for backward compatibility only */
6524         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6525             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6526             SendToICS(ics_prefix);
6527             SendToICS(message);
6528             SendToICS("\n");
6529             return;
6530         }
6531     }
6532     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6533         return;
6534     }
6535     /*
6536      * If the move is illegal, cancel it and redraw the board.
6537      * Also deal with other error cases.  Matching is rather loose
6538      * here to accommodate engines written before the spec.
6539      */
6540     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6541         strncmp(message, "Error", 5) == 0) {
6542         if (StrStr(message, "name") || 
6543             StrStr(message, "rating") || StrStr(message, "?") ||
6544             StrStr(message, "result") || StrStr(message, "board") ||
6545             StrStr(message, "bk") || StrStr(message, "computer") ||
6546             StrStr(message, "variant") || StrStr(message, "hint") ||
6547             StrStr(message, "random") || StrStr(message, "depth") ||
6548             StrStr(message, "accepted")) {
6549             return;
6550         }
6551         if (StrStr(message, "protover")) {
6552           /* Program is responding to input, so it's apparently done
6553              initializing, and this error message indicates it is
6554              protocol version 1.  So we don't need to wait any longer
6555              for it to initialize and send feature commands. */
6556           FeatureDone(cps, 1);
6557           cps->protocolVersion = 1;
6558           return;
6559         }
6560         cps->maybeThinking = FALSE;
6561
6562         if (StrStr(message, "draw")) {
6563             /* Program doesn't have "draw" command */
6564             cps->sendDrawOffers = 0;
6565             return;
6566         }
6567         if (cps->sendTime != 1 &&
6568             (StrStr(message, "time") || StrStr(message, "otim"))) {
6569           /* Program apparently doesn't have "time" or "otim" command */
6570           cps->sendTime = 0;
6571           return;
6572         }
6573         if (StrStr(message, "analyze")) {
6574             cps->analysisSupport = FALSE;
6575             cps->analyzing = FALSE;
6576             Reset(FALSE, TRUE);
6577             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6578             DisplayError(buf2, 0);
6579             return;
6580         }
6581         if (StrStr(message, "(no matching move)st")) {
6582           /* Special kludge for GNU Chess 4 only */
6583           cps->stKludge = TRUE;
6584           SendTimeControl(cps, movesPerSession, timeControl,
6585                           timeIncrement, appData.searchDepth,
6586                           searchTime);
6587           return;
6588         }
6589         if (StrStr(message, "(no matching move)sd")) {
6590           /* Special kludge for GNU Chess 4 only */
6591           cps->sdKludge = TRUE;
6592           SendTimeControl(cps, movesPerSession, timeControl,
6593                           timeIncrement, appData.searchDepth,
6594                           searchTime);
6595           return;
6596         }
6597         if (!StrStr(message, "llegal")) {
6598             return;
6599         }
6600         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6601             gameMode == IcsIdle) return;
6602         if (forwardMostMove <= backwardMostMove) return;
6603         if (pausing) PauseEvent();
6604       if(appData.forceIllegal) {
6605             // [HGM] illegal: machine refused move; force position after move into it
6606           SendToProgram("force\n", cps);
6607           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6608                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6609                 // when black is to move, while there might be nothing on a2 or black
6610                 // might already have the move. So send the board as if white has the move.
6611                 // But first we must change the stm of the engine, as it refused the last move
6612                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6613                 if(WhiteOnMove(forwardMostMove)) {
6614                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6615                     SendBoard(cps, forwardMostMove); // kludgeless board
6616                 } else {
6617                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6618                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6619                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6620                 }
6621           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6622             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6623                  gameMode == TwoMachinesPlay)
6624               SendToProgram("go\n", cps);
6625             return;
6626       } else
6627         if (gameMode == PlayFromGameFile) {
6628             /* Stop reading this game file */
6629             gameMode = EditGame;
6630             ModeHighlight();
6631         }
6632         currentMove = --forwardMostMove;
6633         DisplayMove(currentMove-1); /* before DisplayMoveError */
6634         SwitchClocks();
6635         DisplayBothClocks();
6636         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6637                 parseList[currentMove], cps->which);
6638         DisplayMoveError(buf1);
6639         DrawPosition(FALSE, boards[currentMove]);
6640
6641         /* [HGM] illegal-move claim should forfeit game when Xboard */
6642         /* only passes fully legal moves                            */
6643         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6644             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6645                                 "False illegal-move claim", GE_XBOARD );
6646         }
6647         return;
6648     }
6649     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6650         /* Program has a broken "time" command that
6651            outputs a string not ending in newline.
6652            Don't use it. */
6653         cps->sendTime = 0;
6654     }
6655     
6656     /*
6657      * If chess program startup fails, exit with an error message.
6658      * Attempts to recover here are futile.
6659      */
6660     if ((StrStr(message, "unknown host") != NULL)
6661         || (StrStr(message, "No remote directory") != NULL)
6662         || (StrStr(message, "not found") != NULL)
6663         || (StrStr(message, "No such file") != NULL)
6664         || (StrStr(message, "can't alloc") != NULL)
6665         || (StrStr(message, "Permission denied") != NULL)) {
6666
6667         cps->maybeThinking = FALSE;
6668         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6669                 cps->which, cps->program, cps->host, message);
6670         RemoveInputSource(cps->isr);
6671         DisplayFatalError(buf1, 0, 1);
6672         return;
6673     }
6674     
6675     /* 
6676      * Look for hint output
6677      */
6678     if (sscanf(message, "Hint: %s", buf1) == 1) {
6679         if (cps == &first && hintRequested) {
6680             hintRequested = FALSE;
6681             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6682                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6683                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6684                                     PosFlags(forwardMostMove),
6685                                     fromY, fromX, toY, toX, promoChar, buf1);
6686                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6687                 DisplayInformation(buf2);
6688             } else {
6689                 /* Hint move could not be parsed!? */
6690               snprintf(buf2, sizeof(buf2),
6691                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6692                         buf1, cps->which);
6693                 DisplayError(buf2, 0);
6694             }
6695         } else {
6696             strcpy(lastHint, buf1);
6697         }
6698         return;
6699     }
6700
6701     /*
6702      * Ignore other messages if game is not in progress
6703      */
6704     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6705         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6706
6707     /*
6708      * look for win, lose, draw, or draw offer
6709      */
6710     if (strncmp(message, "1-0", 3) == 0) {
6711         char *p, *q, *r = "";
6712         p = strchr(message, '{');
6713         if (p) {
6714             q = strchr(p, '}');
6715             if (q) {
6716                 *q = NULLCHAR;
6717                 r = p + 1;
6718             }
6719         }
6720         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6721         return;
6722     } else if (strncmp(message, "0-1", 3) == 0) {
6723         char *p, *q, *r = "";
6724         p = strchr(message, '{');
6725         if (p) {
6726             q = strchr(p, '}');
6727             if (q) {
6728                 *q = NULLCHAR;
6729                 r = p + 1;
6730             }
6731         }
6732         /* Kludge for Arasan 4.1 bug */
6733         if (strcmp(r, "Black resigns") == 0) {
6734             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6735             return;
6736         }
6737         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6738         return;
6739     } else if (strncmp(message, "1/2", 3) == 0) {
6740         char *p, *q, *r = "";
6741         p = strchr(message, '{');
6742         if (p) {
6743             q = strchr(p, '}');
6744             if (q) {
6745                 *q = NULLCHAR;
6746                 r = p + 1;
6747             }
6748         }
6749             
6750         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6751         return;
6752
6753     } else if (strncmp(message, "White resign", 12) == 0) {
6754         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6755         return;
6756     } else if (strncmp(message, "Black resign", 12) == 0) {
6757         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6758         return;
6759     } else if (strncmp(message, "White matches", 13) == 0 ||
6760                strncmp(message, "Black matches", 13) == 0   ) {
6761         /* [HGM] ignore GNUShogi noises */
6762         return;
6763     } else if (strncmp(message, "White", 5) == 0 &&
6764                message[5] != '(' &&
6765                StrStr(message, "Black") == NULL) {
6766         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "Black", 5) == 0 &&
6769                message[5] != '(') {
6770         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6771         return;
6772     } else if (strcmp(message, "resign") == 0 ||
6773                strcmp(message, "computer resigns") == 0) {
6774         switch (gameMode) {
6775           case MachinePlaysBlack:
6776           case IcsPlayingBlack:
6777             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6778             break;
6779           case MachinePlaysWhite:
6780           case IcsPlayingWhite:
6781             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6782             break;
6783           case TwoMachinesPlay:
6784             if (cps->twoMachinesColor[0] == 'w')
6785               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6786             else
6787               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6788             break;
6789           default:
6790             /* can't happen */
6791             break;
6792         }
6793         return;
6794     } else if (strncmp(message, "opponent mates", 14) == 0) {
6795         switch (gameMode) {
6796           case MachinePlaysBlack:
6797           case IcsPlayingBlack:
6798             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6799             break;
6800           case MachinePlaysWhite:
6801           case IcsPlayingWhite:
6802             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6803             break;
6804           case TwoMachinesPlay:
6805             if (cps->twoMachinesColor[0] == 'w')
6806               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6807             else
6808               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6809             break;
6810           default:
6811             /* can't happen */
6812             break;
6813         }
6814         return;
6815     } else if (strncmp(message, "computer mates", 14) == 0) {
6816         switch (gameMode) {
6817           case MachinePlaysBlack:
6818           case IcsPlayingBlack:
6819             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6820             break;
6821           case MachinePlaysWhite:
6822           case IcsPlayingWhite:
6823             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6824             break;
6825           case TwoMachinesPlay:
6826             if (cps->twoMachinesColor[0] == 'w')
6827               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6828             else
6829               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6830             break;
6831           default:
6832             /* can't happen */
6833             break;
6834         }
6835         return;
6836     } else if (strncmp(message, "checkmate", 9) == 0) {
6837         if (WhiteOnMove(forwardMostMove)) {
6838             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839         } else {
6840             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6841         }
6842         return;
6843     } else if (strstr(message, "Draw") != NULL ||
6844                strstr(message, "game is a draw") != NULL) {
6845         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6846         return;
6847     } else if (strstr(message, "offer") != NULL &&
6848                strstr(message, "draw") != NULL) {
6849 #if ZIPPY
6850         if (appData.zippyPlay && first.initDone) {
6851             /* Relay offer to ICS */
6852             SendToICS(ics_prefix);
6853             SendToICS("draw\n");
6854         }
6855 #endif
6856         cps->offeredDraw = 2; /* valid until this engine moves twice */
6857         if (gameMode == TwoMachinesPlay) {
6858             if (cps->other->offeredDraw) {
6859                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6860             /* [HGM] in two-machine mode we delay relaying draw offer      */
6861             /* until after we also have move, to see if it is really claim */
6862             }
6863         } else if (gameMode == MachinePlaysWhite ||
6864                    gameMode == MachinePlaysBlack) {
6865           if (userOfferedDraw) {
6866             DisplayInformation(_("Machine accepts your draw offer"));
6867             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6868           } else {
6869             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6870           }
6871         }
6872     }
6873
6874     
6875     /*
6876      * Look for thinking output
6877      */
6878     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6879           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6880                                 ) {
6881         int plylev, mvleft, mvtot, curscore, time;
6882         char mvname[MOVE_LEN];
6883         u64 nodes; // [DM]
6884         char plyext;
6885         int ignore = FALSE;
6886         int prefixHint = FALSE;
6887         mvname[0] = NULLCHAR;
6888
6889         switch (gameMode) {
6890           case MachinePlaysBlack:
6891           case IcsPlayingBlack:
6892             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6893             break;
6894           case MachinePlaysWhite:
6895           case IcsPlayingWhite:
6896             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6897             break;
6898           case AnalyzeMode:
6899           case AnalyzeFile:
6900             break;
6901           case IcsObserving: /* [DM] icsEngineAnalyze */
6902             if (!appData.icsEngineAnalyze) ignore = TRUE;
6903             break;
6904           case TwoMachinesPlay:
6905             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6906                 ignore = TRUE;
6907             }
6908             break;
6909           default:
6910             ignore = TRUE;
6911             break;
6912         }
6913
6914         if (!ignore) {
6915             buf1[0] = NULLCHAR;
6916             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6917                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6918
6919                 if (plyext != ' ' && plyext != '\t') {
6920                     time *= 100;
6921                 }
6922
6923                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6924                 if( cps->scoreIsAbsolute && 
6925                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6926                 {
6927                     curscore = -curscore;
6928                 }
6929
6930
6931                 programStats.depth = plylev;
6932                 programStats.nodes = nodes;
6933                 programStats.time = time;
6934                 programStats.score = curscore;
6935                 programStats.got_only_move = 0;
6936
6937                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6938                         int ticklen;
6939
6940                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6941                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6942                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6943                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6944                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6945                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6946                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6947                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6948                 }
6949
6950                 /* Buffer overflow protection */
6951                 if (buf1[0] != NULLCHAR) {
6952                     if (strlen(buf1) >= sizeof(programStats.movelist)
6953                         && appData.debugMode) {
6954                         fprintf(debugFP,
6955                                 "PV is too long; using the first %u bytes.\n",
6956                                 (unsigned) sizeof(programStats.movelist) - 1);
6957                     }
6958
6959                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6960                 } else {
6961                     sprintf(programStats.movelist, " no PV\n");
6962                 }
6963
6964                 if (programStats.seen_stat) {
6965                     programStats.ok_to_send = 1;
6966                 }
6967
6968                 if (strchr(programStats.movelist, '(') != NULL) {
6969                     programStats.line_is_book = 1;
6970                     programStats.nr_moves = 0;
6971                     programStats.moves_left = 0;
6972                 } else {
6973                     programStats.line_is_book = 0;
6974                 }
6975
6976                 SendProgramStatsToFrontend( cps, &programStats );
6977
6978                 /* 
6979                     [AS] Protect the thinkOutput buffer from overflow... this
6980                     is only useful if buf1 hasn't overflowed first!
6981                 */
6982                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6983                         plylev, 
6984                         (gameMode == TwoMachinesPlay ?
6985                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6986                         ((double) curscore) / 100.0,
6987                         prefixHint ? lastHint : "",
6988                         prefixHint ? " " : "" );
6989
6990                 if( buf1[0] != NULLCHAR ) {
6991                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6992
6993                     if( strlen(buf1) > max_len ) {
6994                         if( appData.debugMode) {
6995                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6996                         }
6997                         buf1[max_len+1] = '\0';
6998                     }
6999
7000                     strcat( thinkOutput, buf1 );
7001                 }
7002
7003                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7004                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7005                     DisplayMove(currentMove - 1);
7006                 }
7007                 return;
7008
7009             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7010                 /* crafty (9.25+) says "(only move) <move>"
7011                  * if there is only 1 legal move
7012                  */
7013                 sscanf(p, "(only move) %s", buf1);
7014                 sprintf(thinkOutput, "%s (only move)", buf1);
7015                 sprintf(programStats.movelist, "%s (only move)", buf1);
7016                 programStats.depth = 1;
7017                 programStats.nr_moves = 1;
7018                 programStats.moves_left = 1;
7019                 programStats.nodes = 1;
7020                 programStats.time = 1;
7021                 programStats.got_only_move = 1;
7022
7023                 /* Not really, but we also use this member to
7024                    mean "line isn't going to change" (Crafty
7025                    isn't searching, so stats won't change) */
7026                 programStats.line_is_book = 1;
7027
7028                 SendProgramStatsToFrontend( cps, &programStats );
7029                 
7030                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7031                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7032                     DisplayMove(currentMove - 1);
7033                 }
7034                 return;
7035             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7036                               &time, &nodes, &plylev, &mvleft,
7037                               &mvtot, mvname) >= 5) {
7038                 /* The stat01: line is from Crafty (9.29+) in response
7039                    to the "." command */
7040                 programStats.seen_stat = 1;
7041                 cps->maybeThinking = TRUE;
7042
7043                 if (programStats.got_only_move || !appData.periodicUpdates)
7044                   return;
7045
7046                 programStats.depth = plylev;
7047                 programStats.time = time;
7048                 programStats.nodes = nodes;
7049                 programStats.moves_left = mvleft;
7050                 programStats.nr_moves = mvtot;
7051                 strcpy(programStats.move_name, mvname);
7052                 programStats.ok_to_send = 1;
7053                 programStats.movelist[0] = '\0';
7054
7055                 SendProgramStatsToFrontend( cps, &programStats );
7056
7057                 return;
7058
7059             } else if (strncmp(message,"++",2) == 0) {
7060                 /* Crafty 9.29+ outputs this */
7061                 programStats.got_fail = 2;
7062                 return;
7063
7064             } else if (strncmp(message,"--",2) == 0) {
7065                 /* Crafty 9.29+ outputs this */
7066                 programStats.got_fail = 1;
7067                 return;
7068
7069             } else if (thinkOutput[0] != NULLCHAR &&
7070                        strncmp(message, "    ", 4) == 0) {
7071                 unsigned message_len;
7072
7073                 p = message;
7074                 while (*p && *p == ' ') p++;
7075
7076                 message_len = strlen( p );
7077
7078                 /* [AS] Avoid buffer overflow */
7079                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7080                     strcat(thinkOutput, " ");
7081                     strcat(thinkOutput, p);
7082                 }
7083
7084                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7085                     strcat(programStats.movelist, " ");
7086                     strcat(programStats.movelist, p);
7087                 }
7088
7089                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7090                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7091                     DisplayMove(currentMove - 1);
7092                 }
7093                 return;
7094             }
7095         }
7096         else {
7097             buf1[0] = NULLCHAR;
7098
7099             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7100                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7101             {
7102                 ChessProgramStats cpstats;
7103
7104                 if (plyext != ' ' && plyext != '\t') {
7105                     time *= 100;
7106                 }
7107
7108                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7109                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7110                     curscore = -curscore;
7111                 }
7112
7113                 cpstats.depth = plylev;
7114                 cpstats.nodes = nodes;
7115                 cpstats.time = time;
7116                 cpstats.score = curscore;
7117                 cpstats.got_only_move = 0;
7118                 cpstats.movelist[0] = '\0';
7119
7120                 if (buf1[0] != NULLCHAR) {
7121                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7122                 }
7123
7124                 cpstats.ok_to_send = 0;
7125                 cpstats.line_is_book = 0;
7126                 cpstats.nr_moves = 0;
7127                 cpstats.moves_left = 0;
7128
7129                 SendProgramStatsToFrontend( cps, &cpstats );
7130             }
7131         }
7132     }
7133 }
7134
7135
7136 /* Parse a game score from the character string "game", and
7137    record it as the history of the current game.  The game
7138    score is NOT assumed to start from the standard position. 
7139    The display is not updated in any way.
7140    */
7141 void
7142 ParseGameHistory(game)
7143      char *game;
7144 {
7145     ChessMove moveType;
7146     int fromX, fromY, toX, toY, boardIndex;
7147     char promoChar;
7148     char *p, *q;
7149     char buf[MSG_SIZ];
7150
7151     if (appData.debugMode)
7152       fprintf(debugFP, "Parsing game history: %s\n", game);
7153
7154     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7155     gameInfo.site = StrSave(appData.icsHost);
7156     gameInfo.date = PGNDate();
7157     gameInfo.round = StrSave("-");
7158
7159     /* Parse out names of players */
7160     while (*game == ' ') game++;
7161     p = buf;
7162     while (*game != ' ') *p++ = *game++;
7163     *p = NULLCHAR;
7164     gameInfo.white = StrSave(buf);
7165     while (*game == ' ') game++;
7166     p = buf;
7167     while (*game != ' ' && *game != '\n') *p++ = *game++;
7168     *p = NULLCHAR;
7169     gameInfo.black = StrSave(buf);
7170
7171     /* Parse moves */
7172     boardIndex = blackPlaysFirst ? 1 : 0;
7173     yynewstr(game);
7174     for (;;) {
7175         yyboardindex = boardIndex;
7176         moveType = (ChessMove) yylex();
7177         switch (moveType) {
7178           case IllegalMove:             /* maybe suicide chess, etc. */
7179   if (appData.debugMode) {
7180     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7181     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7182     setbuf(debugFP, NULL);
7183   }
7184           case WhitePromotionChancellor:
7185           case BlackPromotionChancellor:
7186           case WhitePromotionArchbishop:
7187           case BlackPromotionArchbishop:
7188           case WhitePromotionQueen:
7189           case BlackPromotionQueen:
7190           case WhitePromotionRook:
7191           case BlackPromotionRook:
7192           case WhitePromotionBishop:
7193           case BlackPromotionBishop:
7194           case WhitePromotionKnight:
7195           case BlackPromotionKnight:
7196           case WhitePromotionKing:
7197           case BlackPromotionKing:
7198           case NormalMove:
7199           case WhiteCapturesEnPassant:
7200           case BlackCapturesEnPassant:
7201           case WhiteKingSideCastle:
7202           case WhiteQueenSideCastle:
7203           case BlackKingSideCastle:
7204           case BlackQueenSideCastle:
7205           case WhiteKingSideCastleWild:
7206           case WhiteQueenSideCastleWild:
7207           case BlackKingSideCastleWild:
7208           case BlackQueenSideCastleWild:
7209           /* PUSH Fabien */
7210           case WhiteHSideCastleFR:
7211           case WhiteASideCastleFR:
7212           case BlackHSideCastleFR:
7213           case BlackASideCastleFR:
7214           /* POP Fabien */
7215             fromX = currentMoveString[0] - AAA;
7216             fromY = currentMoveString[1] - ONE;
7217             toX = currentMoveString[2] - AAA;
7218             toY = currentMoveString[3] - ONE;
7219             promoChar = currentMoveString[4];
7220             break;
7221           case WhiteDrop:
7222           case BlackDrop:
7223             fromX = moveType == WhiteDrop ?
7224               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7225             (int) CharToPiece(ToLower(currentMoveString[0]));
7226             fromY = DROP_RANK;
7227             toX = currentMoveString[2] - AAA;
7228             toY = currentMoveString[3] - ONE;
7229             promoChar = NULLCHAR;
7230             break;
7231           case AmbiguousMove:
7232             /* bug? */
7233             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7234   if (appData.debugMode) {
7235     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7236     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7237     setbuf(debugFP, NULL);
7238   }
7239             DisplayError(buf, 0);
7240             return;
7241           case ImpossibleMove:
7242             /* bug? */
7243             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7244   if (appData.debugMode) {
7245     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247     setbuf(debugFP, NULL);
7248   }
7249             DisplayError(buf, 0);
7250             return;
7251           case (ChessMove) 0:   /* end of file */
7252             if (boardIndex < backwardMostMove) {
7253                 /* Oops, gap.  How did that happen? */
7254                 DisplayError(_("Gap in move list"), 0);
7255                 return;
7256             }
7257             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7258             if (boardIndex > forwardMostMove) {
7259                 forwardMostMove = boardIndex;
7260             }
7261             return;
7262           case ElapsedTime:
7263             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7264                 strcat(parseList[boardIndex-1], " ");
7265                 strcat(parseList[boardIndex-1], yy_text);
7266             }
7267             continue;
7268           case Comment:
7269           case PGNTag:
7270           case NAG:
7271           default:
7272             /* ignore */
7273             continue;
7274           case WhiteWins:
7275           case BlackWins:
7276           case GameIsDrawn:
7277           case GameUnfinished:
7278             if (gameMode == IcsExamining) {
7279                 if (boardIndex < backwardMostMove) {
7280                     /* Oops, gap.  How did that happen? */
7281                     return;
7282                 }
7283                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7284                 return;
7285             }
7286             gameInfo.result = moveType;
7287             p = strchr(yy_text, '{');
7288             if (p == NULL) p = strchr(yy_text, '(');
7289             if (p == NULL) {
7290                 p = yy_text;
7291                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7292             } else {
7293                 q = strchr(p, *p == '{' ? '}' : ')');
7294                 if (q != NULL) *q = NULLCHAR;
7295                 p++;
7296             }
7297             gameInfo.resultDetails = StrSave(p);
7298             continue;
7299         }
7300         if (boardIndex >= forwardMostMove &&
7301             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7302             backwardMostMove = blackPlaysFirst ? 1 : 0;
7303             return;
7304         }
7305         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7306                                  fromY, fromX, toY, toX, promoChar,
7307                                  parseList[boardIndex]);
7308         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7309         /* currentMoveString is set as a side-effect of yylex */
7310         strcpy(moveList[boardIndex], currentMoveString);
7311         strcat(moveList[boardIndex], "\n");
7312         boardIndex++;
7313         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7314         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7315           case MT_NONE:
7316           case MT_STALEMATE:
7317           default:
7318             break;
7319           case MT_CHECK:
7320             if(gameInfo.variant != VariantShogi)
7321                 strcat(parseList[boardIndex - 1], "+");
7322             break;
7323           case MT_CHECKMATE:
7324           case MT_STAINMATE:
7325             strcat(parseList[boardIndex - 1], "#");
7326             break;
7327         }
7328     }
7329 }
7330
7331
7332 /* Apply a move to the given board  */
7333 void
7334 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7335      int fromX, fromY, toX, toY;
7336      int promoChar;
7337      Board board;
7338 {
7339   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7340
7341     /* [HGM] compute & store e.p. status and castling rights for new position */
7342     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7343     { int i;
7344
7345       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7346       oldEP = (signed char)board[EP_STATUS];
7347       board[EP_STATUS] = EP_NONE;
7348
7349       if( board[toY][toX] != EmptySquare ) 
7350            board[EP_STATUS] = EP_CAPTURE;  
7351
7352       if( board[fromY][fromX] == WhitePawn ) {
7353            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7354                board[EP_STATUS] = EP_PAWN_MOVE;
7355            if( toY-fromY==2) {
7356                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7357                         gameInfo.variant != VariantBerolina || toX < fromX)
7358                       board[EP_STATUS] = toX | berolina;
7359                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7360                         gameInfo.variant != VariantBerolina || toX > fromX) 
7361                       board[EP_STATUS] = toX;
7362            }
7363       } else 
7364       if( board[fromY][fromX] == BlackPawn ) {
7365            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7366                board[EP_STATUS] = EP_PAWN_MOVE; 
7367            if( toY-fromY== -2) {
7368                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7369                         gameInfo.variant != VariantBerolina || toX < fromX)
7370                       board[EP_STATUS] = toX | berolina;
7371                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7372                         gameInfo.variant != VariantBerolina || toX > fromX) 
7373                       board[EP_STATUS] = toX;
7374            }
7375        }
7376
7377        for(i=0; i<nrCastlingRights; i++) {
7378            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7379               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7380              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7381        }
7382
7383     }
7384
7385   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7386   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7387        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7388          
7389   if (fromX == toX && fromY == toY) return;
7390
7391   if (fromY == DROP_RANK) {
7392         /* must be first */
7393         piece = board[toY][toX] = (ChessSquare) fromX;
7394   } else {
7395      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7396      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7397      if(gameInfo.variant == VariantKnightmate)
7398          king += (int) WhiteUnicorn - (int) WhiteKing;
7399
7400     /* Code added by Tord: */
7401     /* FRC castling assumed when king captures friendly rook. */
7402     if (board[fromY][fromX] == WhiteKing &&
7403              board[toY][toX] == WhiteRook) {
7404       board[fromY][fromX] = EmptySquare;
7405       board[toY][toX] = EmptySquare;
7406       if(toX > fromX) {
7407         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7408       } else {
7409         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7410       }
7411     } else if (board[fromY][fromX] == BlackKing &&
7412                board[toY][toX] == BlackRook) {
7413       board[fromY][fromX] = EmptySquare;
7414       board[toY][toX] = EmptySquare;
7415       if(toX > fromX) {
7416         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7417       } else {
7418         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7419       }
7420     /* End of code added by Tord */
7421
7422     } else if (board[fromY][fromX] == king
7423         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7424         && toY == fromY && toX > fromX+1) {
7425         board[fromY][fromX] = EmptySquare;
7426         board[toY][toX] = king;
7427         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7428         board[fromY][BOARD_RGHT-1] = EmptySquare;
7429     } else if (board[fromY][fromX] == king
7430         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7431                && toY == fromY && toX < fromX-1) {
7432         board[fromY][fromX] = EmptySquare;
7433         board[toY][toX] = king;
7434         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7435         board[fromY][BOARD_LEFT] = EmptySquare;
7436     } else if (board[fromY][fromX] == WhitePawn
7437                && toY == BOARD_HEIGHT-1
7438                && gameInfo.variant != VariantXiangqi
7439                ) {
7440         /* white pawn promotion */
7441         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7442         if (board[toY][toX] == EmptySquare) {
7443             board[toY][toX] = WhiteQueen;
7444         }
7445         if(gameInfo.variant==VariantBughouse ||
7446            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7447             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7448         board[fromY][fromX] = EmptySquare;
7449     } else if ((fromY == BOARD_HEIGHT-4)
7450                && (toX != fromX)
7451                && gameInfo.variant != VariantXiangqi
7452                && gameInfo.variant != VariantBerolina
7453                && (board[fromY][fromX] == WhitePawn)
7454                && (board[toY][toX] == EmptySquare)) {
7455         board[fromY][fromX] = EmptySquare;
7456         board[toY][toX] = WhitePawn;
7457         captured = board[toY - 1][toX];
7458         board[toY - 1][toX] = EmptySquare;
7459     } else if ((fromY == BOARD_HEIGHT-4)
7460                && (toX == fromX)
7461                && gameInfo.variant == VariantBerolina
7462                && (board[fromY][fromX] == WhitePawn)
7463                && (board[toY][toX] == EmptySquare)) {
7464         board[fromY][fromX] = EmptySquare;
7465         board[toY][toX] = WhitePawn;
7466         if(oldEP & EP_BEROLIN_A) {
7467                 captured = board[fromY][fromX-1];
7468                 board[fromY][fromX-1] = EmptySquare;
7469         }else{  captured = board[fromY][fromX+1];
7470                 board[fromY][fromX+1] = EmptySquare;
7471         }
7472     } else if (board[fromY][fromX] == king
7473         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7474                && toY == fromY && toX > fromX+1) {
7475         board[fromY][fromX] = EmptySquare;
7476         board[toY][toX] = king;
7477         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7478         board[fromY][BOARD_RGHT-1] = EmptySquare;
7479     } else if (board[fromY][fromX] == king
7480         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7481                && toY == fromY && toX < fromX-1) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = king;
7484         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7485         board[fromY][BOARD_LEFT] = EmptySquare;
7486     } else if (fromY == 7 && fromX == 3
7487                && board[fromY][fromX] == BlackKing
7488                && toY == 7 && toX == 5) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = BlackKing;
7491         board[fromY][7] = EmptySquare;
7492         board[toY][4] = BlackRook;
7493     } else if (fromY == 7 && fromX == 3
7494                && board[fromY][fromX] == BlackKing
7495                && toY == 7 && toX == 1) {
7496         board[fromY][fromX] = EmptySquare;
7497         board[toY][toX] = BlackKing;
7498         board[fromY][0] = EmptySquare;
7499         board[toY][2] = BlackRook;
7500     } else if (board[fromY][fromX] == BlackPawn
7501                && toY == 0
7502                && gameInfo.variant != VariantXiangqi
7503                ) {
7504         /* black pawn promotion */
7505         board[0][toX] = CharToPiece(ToLower(promoChar));
7506         if (board[0][toX] == EmptySquare) {
7507             board[0][toX] = BlackQueen;
7508         }
7509         if(gameInfo.variant==VariantBughouse ||
7510            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7511             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7512         board[fromY][fromX] = EmptySquare;
7513     } else if ((fromY == 3)
7514                && (toX != fromX)
7515                && gameInfo.variant != VariantXiangqi
7516                && gameInfo.variant != VariantBerolina
7517                && (board[fromY][fromX] == BlackPawn)
7518                && (board[toY][toX] == EmptySquare)) {
7519         board[fromY][fromX] = EmptySquare;
7520         board[toY][toX] = BlackPawn;
7521         captured = board[toY + 1][toX];
7522         board[toY + 1][toX] = EmptySquare;
7523     } else if ((fromY == 3)
7524                && (toX == fromX)
7525                && gameInfo.variant == VariantBerolina
7526                && (board[fromY][fromX] == BlackPawn)
7527                && (board[toY][toX] == EmptySquare)) {
7528         board[fromY][fromX] = EmptySquare;
7529         board[toY][toX] = BlackPawn;
7530         if(oldEP & EP_BEROLIN_A) {
7531                 captured = board[fromY][fromX-1];
7532                 board[fromY][fromX-1] = EmptySquare;
7533         }else{  captured = board[fromY][fromX+1];
7534                 board[fromY][fromX+1] = EmptySquare;
7535         }
7536     } else {
7537         board[toY][toX] = board[fromY][fromX];
7538         board[fromY][fromX] = EmptySquare;
7539     }
7540
7541     /* [HGM] now we promote for Shogi, if needed */
7542     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7543         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7544   }
7545
7546     if (gameInfo.holdingsWidth != 0) {
7547
7548       /* !!A lot more code needs to be written to support holdings  */
7549       /* [HGM] OK, so I have written it. Holdings are stored in the */
7550       /* penultimate board files, so they are automaticlly stored   */
7551       /* in the game history.                                       */
7552       if (fromY == DROP_RANK) {
7553         /* Delete from holdings, by decreasing count */
7554         /* and erasing image if necessary            */
7555         p = (int) fromX;
7556         if(p < (int) BlackPawn) { /* white drop */
7557              p -= (int)WhitePawn;
7558                  p = PieceToNumber((ChessSquare)p);
7559              if(p >= gameInfo.holdingsSize) p = 0;
7560              if(--board[p][BOARD_WIDTH-2] <= 0)
7561                   board[p][BOARD_WIDTH-1] = EmptySquare;
7562              if((int)board[p][BOARD_WIDTH-2] < 0)
7563                         board[p][BOARD_WIDTH-2] = 0;
7564         } else {                  /* black drop */
7565              p -= (int)BlackPawn;
7566                  p = PieceToNumber((ChessSquare)p);
7567              if(p >= gameInfo.holdingsSize) p = 0;
7568              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7569                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7570              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7571                         board[BOARD_HEIGHT-1-p][1] = 0;
7572         }
7573       }
7574       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7575           && gameInfo.variant != VariantBughouse        ) {
7576         /* [HGM] holdings: Add to holdings, if holdings exist */
7577         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7578                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7579                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7580         }
7581         p = (int) captured;
7582         if (p >= (int) BlackPawn) {
7583           p -= (int)BlackPawn;
7584           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7585                   /* in Shogi restore piece to its original  first */
7586                   captured = (ChessSquare) (DEMOTED captured);
7587                   p = DEMOTED p;
7588           }
7589           p = PieceToNumber((ChessSquare)p);
7590           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7591           board[p][BOARD_WIDTH-2]++;
7592           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7593         } else {
7594           p -= (int)WhitePawn;
7595           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7596                   captured = (ChessSquare) (DEMOTED captured);
7597                   p = DEMOTED p;
7598           }
7599           p = PieceToNumber((ChessSquare)p);
7600           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7601           board[BOARD_HEIGHT-1-p][1]++;
7602           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7603         }
7604       }
7605     } else if (gameInfo.variant == VariantAtomic) {
7606       if (captured != EmptySquare) {
7607         int y, x;
7608         for (y = toY-1; y <= toY+1; y++) {
7609           for (x = toX-1; x <= toX+1; x++) {
7610             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7611                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7612               board[y][x] = EmptySquare;
7613             }
7614           }
7615         }
7616         board[toY][toX] = EmptySquare;
7617       }
7618     }
7619     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7620         /* [HGM] Shogi promotions */
7621         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7622     }
7623
7624     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7625                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7626         // [HGM] superchess: take promotion piece out of holdings
7627         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7628         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7629             if(!--board[k][BOARD_WIDTH-2])
7630                 board[k][BOARD_WIDTH-1] = EmptySquare;
7631         } else {
7632             if(!--board[BOARD_HEIGHT-1-k][1])
7633                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7634         }
7635     }
7636
7637 }
7638
7639 /* Updates forwardMostMove */
7640 void
7641 MakeMove(fromX, fromY, toX, toY, promoChar)
7642      int fromX, fromY, toX, toY;
7643      int promoChar;
7644 {
7645 //    forwardMostMove++; // [HGM] bare: moved downstream
7646
7647     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7648         int timeLeft; static int lastLoadFlag=0; int king, piece;
7649         piece = boards[forwardMostMove][fromY][fromX];
7650         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7651         if(gameInfo.variant == VariantKnightmate)
7652             king += (int) WhiteUnicorn - (int) WhiteKing;
7653         if(forwardMostMove == 0) {
7654             if(blackPlaysFirst) 
7655                 fprintf(serverMoves, "%s;", second.tidy);
7656             fprintf(serverMoves, "%s;", first.tidy);
7657             if(!blackPlaysFirst) 
7658                 fprintf(serverMoves, "%s;", second.tidy);
7659         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7660         lastLoadFlag = loadFlag;
7661         // print base move
7662         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7663         // print castling suffix
7664         if( toY == fromY && piece == king ) {
7665             if(toX-fromX > 1)
7666                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7667             if(fromX-toX >1)
7668                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7669         }
7670         // e.p. suffix
7671         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7672              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7673              boards[forwardMostMove][toY][toX] == EmptySquare
7674              && fromX != toX )
7675                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7676         // promotion suffix
7677         if(promoChar != NULLCHAR)
7678                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7679         if(!loadFlag) {
7680             fprintf(serverMoves, "/%d/%d",
7681                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7682             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7683             else                      timeLeft = blackTimeRemaining/1000;
7684             fprintf(serverMoves, "/%d", timeLeft);
7685         }
7686         fflush(serverMoves);
7687     }
7688
7689     if (forwardMostMove+1 >= MAX_MOVES) {
7690       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7691                         0, 1);
7692       return;
7693     }
7694     if (commentList[forwardMostMove+1] != NULL) {
7695         free(commentList[forwardMostMove+1]);
7696         commentList[forwardMostMove+1] = NULL;
7697     }
7698     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7699     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7700     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7701     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7702     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7703     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7704     gameInfo.result = GameUnfinished;
7705     if (gameInfo.resultDetails != NULL) {
7706         free(gameInfo.resultDetails);
7707         gameInfo.resultDetails = NULL;
7708     }
7709     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7710                               moveList[forwardMostMove - 1]);
7711     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7712                              PosFlags(forwardMostMove - 1),
7713                              fromY, fromX, toY, toX, promoChar,
7714                              parseList[forwardMostMove - 1]);
7715     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7716       case MT_NONE:
7717       case MT_STALEMATE:
7718       default:
7719         break;
7720       case MT_CHECK:
7721         if(gameInfo.variant != VariantShogi)
7722             strcat(parseList[forwardMostMove - 1], "+");
7723         break;
7724       case MT_CHECKMATE:
7725       case MT_STAINMATE:
7726         strcat(parseList[forwardMostMove - 1], "#");
7727         break;
7728     }
7729     if (appData.debugMode) {
7730         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7731     }
7732
7733 }
7734
7735 /* Updates currentMove if not pausing */
7736 void
7737 ShowMove(fromX, fromY, toX, toY)
7738 {
7739     int instant = (gameMode == PlayFromGameFile) ?
7740         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7741     if(appData.noGUI) return;
7742     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7743         if (!instant) {
7744             if (forwardMostMove == currentMove + 1) {
7745                 AnimateMove(boards[forwardMostMove - 1],
7746                             fromX, fromY, toX, toY);
7747             }
7748             if (appData.highlightLastMove) {
7749                 SetHighlights(fromX, fromY, toX, toY);
7750             }
7751         }
7752         currentMove = forwardMostMove;
7753     }
7754
7755     if (instant) return;
7756
7757     DisplayMove(currentMove - 1);
7758     DrawPosition(FALSE, boards[currentMove]);
7759     DisplayBothClocks();
7760     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7761 }
7762
7763 void SendEgtPath(ChessProgramState *cps)
7764 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7765         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7766
7767         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7768
7769         while(*p) {
7770             char c, *q = name+1, *r, *s;
7771
7772             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7773             while(*p && *p != ',') *q++ = *p++;
7774             *q++ = ':'; *q = 0;
7775             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7776                 strcmp(name, ",nalimov:") == 0 ) {
7777                 // take nalimov path from the menu-changeable option first, if it is defined
7778                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7779                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7780             } else
7781             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7782                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7783                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7784                 s = r = StrStr(s, ":") + 1; // beginning of path info
7785                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7786                 c = *r; *r = 0;             // temporarily null-terminate path info
7787                     *--q = 0;               // strip of trailig ':' from name
7788                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7789                 *r = c;
7790                 SendToProgram(buf,cps);     // send egtbpath command for this format
7791             }
7792             if(*p == ',') p++; // read away comma to position for next format name
7793         }
7794 }
7795
7796 void
7797 InitChessProgram(cps, setup)
7798      ChessProgramState *cps;
7799      int setup; /* [HGM] needed to setup FRC opening position */
7800 {
7801     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7802     if (appData.noChessProgram) return;
7803     hintRequested = FALSE;
7804     bookRequested = FALSE;
7805
7806     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7807     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7808     if(cps->memSize) { /* [HGM] memory */
7809         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7810         SendToProgram(buf, cps);
7811     }
7812     SendEgtPath(cps); /* [HGM] EGT */
7813     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7814         sprintf(buf, "cores %d\n", appData.smpCores);
7815         SendToProgram(buf, cps);
7816     }
7817
7818     SendToProgram(cps->initString, cps);
7819     if (gameInfo.variant != VariantNormal &&
7820         gameInfo.variant != VariantLoadable
7821         /* [HGM] also send variant if board size non-standard */
7822         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7823                                             ) {
7824       char *v = VariantName(gameInfo.variant);
7825       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7826         /* [HGM] in protocol 1 we have to assume all variants valid */
7827         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7828         DisplayFatalError(buf, 0, 1);
7829         return;
7830       }
7831
7832       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7833       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7834       if( gameInfo.variant == VariantXiangqi )
7835            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7836       if( gameInfo.variant == VariantShogi )
7837            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7838       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7839            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7840       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7841                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7842            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7843       if( gameInfo.variant == VariantCourier )
7844            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7845       if( gameInfo.variant == VariantSuper )
7846            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7847       if( gameInfo.variant == VariantGreat )
7848            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7849
7850       if(overruled) {
7851            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7852                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7853            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7854            if(StrStr(cps->variants, b) == NULL) { 
7855                // specific sized variant not known, check if general sizing allowed
7856                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7857                    if(StrStr(cps->variants, "boardsize") == NULL) {
7858                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7859                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7860                        DisplayFatalError(buf, 0, 1);
7861                        return;
7862                    }
7863                    /* [HGM] here we really should compare with the maximum supported board size */
7864                }
7865            }
7866       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7867       sprintf(buf, "variant %s\n", b);
7868       SendToProgram(buf, cps);
7869     }
7870     currentlyInitializedVariant = gameInfo.variant;
7871
7872     /* [HGM] send opening position in FRC to first engine */
7873     if(setup) {
7874           SendToProgram("force\n", cps);
7875           SendBoard(cps, 0);
7876           /* engine is now in force mode! Set flag to wake it up after first move. */
7877           setboardSpoiledMachineBlack = 1;
7878     }
7879
7880     if (cps->sendICS) {
7881       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7882       SendToProgram(buf, cps);
7883     }
7884     cps->maybeThinking = FALSE;
7885     cps->offeredDraw = 0;
7886     if (!appData.icsActive) {
7887         SendTimeControl(cps, movesPerSession, timeControl,
7888                         timeIncrement, appData.searchDepth,
7889                         searchTime);
7890     }
7891     if (appData.showThinking 
7892         // [HGM] thinking: four options require thinking output to be sent
7893         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7894                                 ) {
7895         SendToProgram("post\n", cps);
7896     }
7897     SendToProgram("hard\n", cps);
7898     if (!appData.ponderNextMove) {
7899         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7900            it without being sure what state we are in first.  "hard"
7901            is not a toggle, so that one is OK.
7902          */
7903         SendToProgram("easy\n", cps);
7904     }
7905     if (cps->usePing) {
7906       sprintf(buf, "ping %d\n", ++cps->lastPing);
7907       SendToProgram(buf, cps);
7908     }
7909     cps->initDone = TRUE;
7910 }   
7911
7912
7913 void
7914 StartChessProgram(cps)
7915      ChessProgramState *cps;
7916 {
7917     char buf[MSG_SIZ];
7918     int err;
7919
7920     if (appData.noChessProgram) return;
7921     cps->initDone = FALSE;
7922
7923     if (strcmp(cps->host, "localhost") == 0) {
7924         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7925     } else if (*appData.remoteShell == NULLCHAR) {
7926         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7927     } else {
7928         if (*appData.remoteUser == NULLCHAR) {
7929           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7930                     cps->program);
7931         } else {
7932           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7933                     cps->host, appData.remoteUser, cps->program);
7934         }
7935         err = StartChildProcess(buf, "", &cps->pr);
7936     }
7937     
7938     if (err != 0) {
7939         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7940         DisplayFatalError(buf, err, 1);
7941         cps->pr = NoProc;
7942         cps->isr = NULL;
7943         return;
7944     }
7945     
7946     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7947     if (cps->protocolVersion > 1) {
7948       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7949       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7950       cps->comboCnt = 0;  //                and values of combo boxes
7951       SendToProgram(buf, cps);
7952     } else {
7953       SendToProgram("xboard\n", cps);
7954     }
7955 }
7956
7957
7958 void
7959 TwoMachinesEventIfReady P((void))
7960 {
7961   if (first.lastPing != first.lastPong) {
7962     DisplayMessage("", _("Waiting for first chess program"));
7963     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7964     return;
7965   }
7966   if (second.lastPing != second.lastPong) {
7967     DisplayMessage("", _("Waiting for second chess program"));
7968     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7969     return;
7970   }
7971   ThawUI();
7972   TwoMachinesEvent();
7973 }
7974
7975 void
7976 NextMatchGame P((void))
7977 {
7978     int index; /* [HGM] autoinc: step load index during match */
7979     Reset(FALSE, TRUE);
7980     if (*appData.loadGameFile != NULLCHAR) {
7981         index = appData.loadGameIndex;
7982         if(index < 0) { // [HGM] autoinc
7983             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7984             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7985         } 
7986         LoadGameFromFile(appData.loadGameFile,
7987                          index,
7988                          appData.loadGameFile, FALSE);
7989     } else if (*appData.loadPositionFile != NULLCHAR) {
7990         index = appData.loadPositionIndex;
7991         if(index < 0) { // [HGM] autoinc
7992             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7993             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7994         } 
7995         LoadPositionFromFile(appData.loadPositionFile,
7996                              index,
7997                              appData.loadPositionFile);
7998     }
7999     TwoMachinesEventIfReady();
8000 }
8001
8002 void UserAdjudicationEvent( int result )
8003 {
8004     ChessMove gameResult = GameIsDrawn;
8005
8006     if( result > 0 ) {
8007         gameResult = WhiteWins;
8008     }
8009     else if( result < 0 ) {
8010         gameResult = BlackWins;
8011     }
8012
8013     if( gameMode == TwoMachinesPlay ) {
8014         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8015     }
8016 }
8017
8018
8019 // [HGM] save: calculate checksum of game to make games easily identifiable
8020 int StringCheckSum(char *s)
8021 {
8022         int i = 0;
8023         if(s==NULL) return 0;
8024         while(*s) i = i*259 + *s++;
8025         return i;
8026 }
8027
8028 int GameCheckSum()
8029 {
8030         int i, sum=0;
8031         for(i=backwardMostMove; i<forwardMostMove; i++) {
8032                 sum += pvInfoList[i].depth;
8033                 sum += StringCheckSum(parseList[i]);
8034                 sum += StringCheckSum(commentList[i]);
8035                 sum *= 261;
8036         }
8037         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8038         return sum + StringCheckSum(commentList[i]);
8039 } // end of save patch
8040
8041 void
8042 GameEnds(result, resultDetails, whosays)
8043      ChessMove result;
8044      char *resultDetails;
8045      int whosays;
8046 {
8047     GameMode nextGameMode;
8048     int isIcsGame;
8049     char buf[MSG_SIZ];
8050
8051     if(endingGame) return; /* [HGM] crash: forbid recursion */
8052     endingGame = 1;
8053
8054     if (appData.debugMode) {
8055       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8056               result, resultDetails ? resultDetails : "(null)", whosays);
8057     }
8058
8059     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8060         /* If we are playing on ICS, the server decides when the
8061            game is over, but the engine can offer to draw, claim 
8062            a draw, or resign. 
8063          */
8064 #if ZIPPY
8065         if (appData.zippyPlay && first.initDone) {
8066             if (result == GameIsDrawn) {
8067                 /* In case draw still needs to be claimed */
8068                 SendToICS(ics_prefix);
8069                 SendToICS("draw\n");
8070             } else if (StrCaseStr(resultDetails, "resign")) {
8071                 SendToICS(ics_prefix);
8072                 SendToICS("resign\n");
8073             }
8074         }
8075 #endif
8076         endingGame = 0; /* [HGM] crash */
8077         return;
8078     }
8079
8080     /* If we're loading the game from a file, stop */
8081     if (whosays == GE_FILE) {
8082       (void) StopLoadGameTimer();
8083       gameFileFP = NULL;
8084     }
8085
8086     /* Cancel draw offers */
8087     first.offeredDraw = second.offeredDraw = 0;
8088
8089     /* If this is an ICS game, only ICS can really say it's done;
8090        if not, anyone can. */
8091     isIcsGame = (gameMode == IcsPlayingWhite || 
8092                  gameMode == IcsPlayingBlack || 
8093                  gameMode == IcsObserving    || 
8094                  gameMode == IcsExamining);
8095
8096     if (!isIcsGame || whosays == GE_ICS) {
8097         /* OK -- not an ICS game, or ICS said it was done */
8098         StopClocks();
8099         if (!isIcsGame && !appData.noChessProgram) 
8100           SetUserThinkingEnables();
8101     
8102         /* [HGM] if a machine claims the game end we verify this claim */
8103         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8104             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8105                 char claimer;
8106                 ChessMove trueResult = (ChessMove) -1;
8107
8108                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8109                                             first.twoMachinesColor[0] :
8110                                             second.twoMachinesColor[0] ;
8111
8112                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8113                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8114                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8115                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8116                 } else
8117                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8118                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8119                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8120                 } else
8121                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8122                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8123                 }
8124
8125                 // now verify win claims, but not in drop games, as we don't understand those yet
8126                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8127                                                  || gameInfo.variant == VariantGreat) &&
8128                     (result == WhiteWins && claimer == 'w' ||
8129                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8130                       if (appData.debugMode) {
8131                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8132                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8133                       }
8134                       if(result != trueResult) {
8135                               sprintf(buf, "False win claim: '%s'", resultDetails);
8136                               result = claimer == 'w' ? BlackWins : WhiteWins;
8137                               resultDetails = buf;
8138                       }
8139                 } else
8140                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8141                     && (forwardMostMove <= backwardMostMove ||
8142                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8143                         (claimer=='b')==(forwardMostMove&1))
8144                                                                                   ) {
8145                       /* [HGM] verify: draws that were not flagged are false claims */
8146                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8147                       result = claimer == 'w' ? BlackWins : WhiteWins;
8148                       resultDetails = buf;
8149                 }
8150                 /* (Claiming a loss is accepted no questions asked!) */
8151             }
8152             /* [HGM] bare: don't allow bare King to win */
8153             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8154                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8155                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8156                && result != GameIsDrawn)
8157             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8158                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8159                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8160                         if(p >= 0 && p <= (int)WhiteKing) k++;
8161                 }
8162                 if (appData.debugMode) {
8163                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8164                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8165                 }
8166                 if(k <= 1) {
8167                         result = GameIsDrawn;
8168                         sprintf(buf, "%s but bare king", resultDetails);
8169                         resultDetails = buf;
8170                 }
8171             }
8172         }
8173
8174
8175         if(serverMoves != NULL && !loadFlag) { char c = '=';
8176             if(result==WhiteWins) c = '+';
8177             if(result==BlackWins) c = '-';
8178             if(resultDetails != NULL)
8179                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8180         }
8181         if (resultDetails != NULL) {
8182             gameInfo.result = result;
8183             gameInfo.resultDetails = StrSave(resultDetails);
8184
8185             /* display last move only if game was not loaded from file */
8186             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8187                 DisplayMove(currentMove - 1);
8188     
8189             if (forwardMostMove != 0) {
8190                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8191                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8192                                                                 ) {
8193                     if (*appData.saveGameFile != NULLCHAR) {
8194                         SaveGameToFile(appData.saveGameFile, TRUE);
8195                     } else if (appData.autoSaveGames) {
8196                         AutoSaveGame();
8197                     }
8198                     if (*appData.savePositionFile != NULLCHAR) {
8199                         SavePositionToFile(appData.savePositionFile);
8200                     }
8201                 }
8202             }
8203
8204             /* Tell program how game ended in case it is learning */
8205             /* [HGM] Moved this to after saving the PGN, just in case */
8206             /* engine died and we got here through time loss. In that */
8207             /* case we will get a fatal error writing the pipe, which */
8208             /* would otherwise lose us the PGN.                       */
8209             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8210             /* output during GameEnds should never be fatal anymore   */
8211             if (gameMode == MachinePlaysWhite ||
8212                 gameMode == MachinePlaysBlack ||
8213                 gameMode == TwoMachinesPlay ||
8214                 gameMode == IcsPlayingWhite ||
8215                 gameMode == IcsPlayingBlack ||
8216                 gameMode == BeginningOfGame) {
8217                 char buf[MSG_SIZ];
8218                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8219                         resultDetails);
8220                 if (first.pr != NoProc) {
8221                     SendToProgram(buf, &first);
8222                 }
8223                 if (second.pr != NoProc &&
8224                     gameMode == TwoMachinesPlay) {
8225                     SendToProgram(buf, &second);
8226                 }
8227             }
8228         }
8229
8230         if (appData.icsActive) {
8231             if (appData.quietPlay &&
8232                 (gameMode == IcsPlayingWhite ||
8233                  gameMode == IcsPlayingBlack)) {
8234                 SendToICS(ics_prefix);
8235                 SendToICS("set shout 1\n");
8236             }
8237             nextGameMode = IcsIdle;
8238             ics_user_moved = FALSE;
8239             /* clean up premove.  It's ugly when the game has ended and the
8240              * premove highlights are still on the board.
8241              */
8242             if (gotPremove) {
8243               gotPremove = FALSE;
8244               ClearPremoveHighlights();
8245               DrawPosition(FALSE, boards[currentMove]);
8246             }
8247             if (whosays == GE_ICS) {
8248                 switch (result) {
8249                 case WhiteWins:
8250                     if (gameMode == IcsPlayingWhite)
8251                         PlayIcsWinSound();
8252                     else if(gameMode == IcsPlayingBlack)
8253                         PlayIcsLossSound();
8254                     break;
8255                 case BlackWins:
8256                     if (gameMode == IcsPlayingBlack)
8257                         PlayIcsWinSound();
8258                     else if(gameMode == IcsPlayingWhite)
8259                         PlayIcsLossSound();
8260                     break;
8261                 case GameIsDrawn:
8262                     PlayIcsDrawSound();
8263                     break;
8264                 default:
8265                     PlayIcsUnfinishedSound();
8266                 }
8267             }
8268         } else if (gameMode == EditGame ||
8269                    gameMode == PlayFromGameFile || 
8270                    gameMode == AnalyzeMode || 
8271                    gameMode == AnalyzeFile) {
8272             nextGameMode = gameMode;
8273         } else {
8274             nextGameMode = EndOfGame;
8275         }
8276         pausing = FALSE;
8277         ModeHighlight();
8278     } else {
8279         nextGameMode = gameMode;
8280     }
8281
8282     if (appData.noChessProgram) {
8283         gameMode = nextGameMode;
8284         ModeHighlight();
8285         endingGame = 0; /* [HGM] crash */
8286         return;
8287     }
8288
8289     if (first.reuse) {
8290         /* Put first chess program into idle state */
8291         if (first.pr != NoProc &&
8292             (gameMode == MachinePlaysWhite ||
8293              gameMode == MachinePlaysBlack ||
8294              gameMode == TwoMachinesPlay ||
8295              gameMode == IcsPlayingWhite ||
8296              gameMode == IcsPlayingBlack ||
8297              gameMode == BeginningOfGame)) {
8298             SendToProgram("force\n", &first);
8299             if (first.usePing) {
8300               char buf[MSG_SIZ];
8301               sprintf(buf, "ping %d\n", ++first.lastPing);
8302               SendToProgram(buf, &first);
8303             }
8304         }
8305     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8306         /* Kill off first chess program */
8307         if (first.isr != NULL)
8308           RemoveInputSource(first.isr);
8309         first.isr = NULL;
8310     
8311         if (first.pr != NoProc) {
8312             ExitAnalyzeMode();
8313             DoSleep( appData.delayBeforeQuit );
8314             SendToProgram("quit\n", &first);
8315             DoSleep( appData.delayAfterQuit );
8316             DestroyChildProcess(first.pr, first.useSigterm);
8317         }
8318         first.pr = NoProc;
8319     }
8320     if (second.reuse) {
8321         /* Put second chess program into idle state */
8322         if (second.pr != NoProc &&
8323             gameMode == TwoMachinesPlay) {
8324             SendToProgram("force\n", &second);
8325             if (second.usePing) {
8326               char buf[MSG_SIZ];
8327               sprintf(buf, "ping %d\n", ++second.lastPing);
8328               SendToProgram(buf, &second);
8329             }
8330         }
8331     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8332         /* Kill off second chess program */
8333         if (second.isr != NULL)
8334           RemoveInputSource(second.isr);
8335         second.isr = NULL;
8336     
8337         if (second.pr != NoProc) {
8338             DoSleep( appData.delayBeforeQuit );
8339             SendToProgram("quit\n", &second);
8340             DoSleep( appData.delayAfterQuit );
8341             DestroyChildProcess(second.pr, second.useSigterm);
8342         }
8343         second.pr = NoProc;
8344     }
8345
8346     if (matchMode && gameMode == TwoMachinesPlay) {
8347         switch (result) {
8348         case WhiteWins:
8349           if (first.twoMachinesColor[0] == 'w') {
8350             first.matchWins++;
8351           } else {
8352             second.matchWins++;
8353           }
8354           break;
8355         case BlackWins:
8356           if (first.twoMachinesColor[0] == 'b') {
8357             first.matchWins++;
8358           } else {
8359             second.matchWins++;
8360           }
8361           break;
8362         default:
8363           break;
8364         }
8365         if (matchGame < appData.matchGames) {
8366             char *tmp;
8367             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8368                 tmp = first.twoMachinesColor;
8369                 first.twoMachinesColor = second.twoMachinesColor;
8370                 second.twoMachinesColor = tmp;
8371             }
8372             gameMode = nextGameMode;
8373             matchGame++;
8374             if(appData.matchPause>10000 || appData.matchPause<10)
8375                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8376             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8377             endingGame = 0; /* [HGM] crash */
8378             return;
8379         } else {
8380             char buf[MSG_SIZ];
8381             gameMode = nextGameMode;
8382             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8383                     first.tidy, second.tidy,
8384                     first.matchWins, second.matchWins,
8385                     appData.matchGames - (first.matchWins + second.matchWins));
8386             DisplayFatalError(buf, 0, 0);
8387         }
8388     }
8389     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8390         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8391       ExitAnalyzeMode();
8392     gameMode = nextGameMode;
8393     ModeHighlight();
8394     endingGame = 0;  /* [HGM] crash */
8395 }
8396
8397 /* Assumes program was just initialized (initString sent).
8398    Leaves program in force mode. */
8399 void
8400 FeedMovesToProgram(cps, upto) 
8401      ChessProgramState *cps;
8402      int upto;
8403 {
8404     int i;
8405     
8406     if (appData.debugMode)
8407       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8408               startedFromSetupPosition ? "position and " : "",
8409               backwardMostMove, upto, cps->which);
8410     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8411         // [HGM] variantswitch: make engine aware of new variant
8412         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8413                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8414         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8415         SendToProgram(buf, cps);
8416         currentlyInitializedVariant = gameInfo.variant;
8417     }
8418     SendToProgram("force\n", cps);
8419     if (startedFromSetupPosition) {
8420         SendBoard(cps, backwardMostMove);
8421     if (appData.debugMode) {
8422         fprintf(debugFP, "feedMoves\n");
8423     }
8424     }
8425     for (i = backwardMostMove; i < upto; i++) {
8426         SendMoveToProgram(i, cps);
8427     }
8428 }
8429
8430
8431 void
8432 ResurrectChessProgram()
8433 {
8434      /* The chess program may have exited.
8435         If so, restart it and feed it all the moves made so far. */
8436
8437     if (appData.noChessProgram || first.pr != NoProc) return;
8438     
8439     StartChessProgram(&first);
8440     InitChessProgram(&first, FALSE);
8441     FeedMovesToProgram(&first, currentMove);
8442
8443     if (!first.sendTime) {
8444         /* can't tell gnuchess what its clock should read,
8445            so we bow to its notion. */
8446         ResetClocks();
8447         timeRemaining[0][currentMove] = whiteTimeRemaining;
8448         timeRemaining[1][currentMove] = blackTimeRemaining;
8449     }
8450
8451     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8452                 appData.icsEngineAnalyze) && first.analysisSupport) {
8453       SendToProgram("analyze\n", &first);
8454       first.analyzing = TRUE;
8455     }
8456 }
8457
8458 /*
8459  * Button procedures
8460  */
8461 void
8462 Reset(redraw, init)
8463      int redraw, init;
8464 {
8465     int i;
8466
8467     if (appData.debugMode) {
8468         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8469                 redraw, init, gameMode);
8470     }
8471     pausing = pauseExamInvalid = FALSE;
8472     startedFromSetupPosition = blackPlaysFirst = FALSE;
8473     firstMove = TRUE;
8474     whiteFlag = blackFlag = FALSE;
8475     userOfferedDraw = FALSE;
8476     hintRequested = bookRequested = FALSE;
8477     first.maybeThinking = FALSE;
8478     second.maybeThinking = FALSE;
8479     first.bookSuspend = FALSE; // [HGM] book
8480     second.bookSuspend = FALSE;
8481     thinkOutput[0] = NULLCHAR;
8482     lastHint[0] = NULLCHAR;
8483     ClearGameInfo(&gameInfo);
8484     gameInfo.variant = StringToVariant(appData.variant);
8485     ics_user_moved = ics_clock_paused = FALSE;
8486     ics_getting_history = H_FALSE;
8487     ics_gamenum = -1;
8488     white_holding[0] = black_holding[0] = NULLCHAR;
8489     ClearProgramStats();
8490     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8491     
8492     ResetFrontEnd();
8493     ClearHighlights();
8494     flipView = appData.flipView;
8495     ClearPremoveHighlights();
8496     gotPremove = FALSE;
8497     alarmSounded = FALSE;
8498
8499     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8500     if(appData.serverMovesName != NULL) {
8501         /* [HGM] prepare to make moves file for broadcasting */
8502         clock_t t = clock();
8503         if(serverMoves != NULL) fclose(serverMoves);
8504         serverMoves = fopen(appData.serverMovesName, "r");
8505         if(serverMoves != NULL) {
8506             fclose(serverMoves);
8507             /* delay 15 sec before overwriting, so all clients can see end */
8508             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8509         }
8510         serverMoves = fopen(appData.serverMovesName, "w");
8511     }
8512
8513     ExitAnalyzeMode();
8514     gameMode = BeginningOfGame;
8515     ModeHighlight();
8516     if(appData.icsActive) gameInfo.variant = VariantNormal;
8517     currentMove = forwardMostMove = backwardMostMove = 0;
8518     InitPosition(redraw);
8519     for (i = 0; i < MAX_MOVES; i++) {
8520         if (commentList[i] != NULL) {
8521             free(commentList[i]);
8522             commentList[i] = NULL;
8523         }
8524     }
8525     ResetClocks();
8526     timeRemaining[0][0] = whiteTimeRemaining;
8527     timeRemaining[1][0] = blackTimeRemaining;
8528     if (first.pr == NULL) {
8529         StartChessProgram(&first);
8530     }
8531     if (init) {
8532             InitChessProgram(&first, startedFromSetupPosition);
8533     }
8534     DisplayTitle("");
8535     DisplayMessage("", "");
8536     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8537     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8538 }
8539
8540 void
8541 AutoPlayGameLoop()
8542 {
8543     for (;;) {
8544         if (!AutoPlayOneMove())
8545           return;
8546         if (matchMode || appData.timeDelay == 0)
8547           continue;
8548         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8549           return;
8550         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8551         break;
8552     }
8553 }
8554
8555
8556 int
8557 AutoPlayOneMove()
8558 {
8559     int fromX, fromY, toX, toY;
8560
8561     if (appData.debugMode) {
8562       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8563     }
8564
8565     if (gameMode != PlayFromGameFile)
8566       return FALSE;
8567
8568     if (currentMove >= forwardMostMove) {
8569       gameMode = EditGame;
8570       ModeHighlight();
8571
8572       /* [AS] Clear current move marker at the end of a game */
8573       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8574
8575       return FALSE;
8576     }
8577     
8578     toX = moveList[currentMove][2] - AAA;
8579     toY = moveList[currentMove][3] - ONE;
8580
8581     if (moveList[currentMove][1] == '@') {
8582         if (appData.highlightLastMove) {
8583             SetHighlights(-1, -1, toX, toY);
8584         }
8585     } else {
8586         fromX = moveList[currentMove][0] - AAA;
8587         fromY = moveList[currentMove][1] - ONE;
8588
8589         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8590
8591         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8592
8593         if (appData.highlightLastMove) {
8594             SetHighlights(fromX, fromY, toX, toY);
8595         }
8596     }
8597     DisplayMove(currentMove);
8598     SendMoveToProgram(currentMove++, &first);
8599     DisplayBothClocks();
8600     DrawPosition(FALSE, boards[currentMove]);
8601     // [HGM] PV info: always display, routine tests if empty
8602     DisplayComment(currentMove - 1, commentList[currentMove]);
8603     return TRUE;
8604 }
8605
8606
8607 int
8608 LoadGameOneMove(readAhead)
8609      ChessMove readAhead;
8610 {
8611     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8612     char promoChar = NULLCHAR;
8613     ChessMove moveType;
8614     char move[MSG_SIZ];
8615     char *p, *q;
8616     
8617     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8618         gameMode != AnalyzeMode && gameMode != Training) {
8619         gameFileFP = NULL;
8620         return FALSE;
8621     }
8622     
8623     yyboardindex = forwardMostMove;
8624     if (readAhead != (ChessMove)0) {
8625       moveType = readAhead;
8626     } else {
8627       if (gameFileFP == NULL)
8628           return FALSE;
8629       moveType = (ChessMove) yylex();
8630     }
8631     
8632     done = FALSE;
8633     switch (moveType) {
8634       case Comment:
8635         if (appData.debugMode) 
8636           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8637         p = yy_text;
8638
8639         /* append the comment but don't display it */
8640         AppendComment(currentMove, p, FALSE);
8641         return TRUE;
8642
8643       case WhiteCapturesEnPassant:
8644       case BlackCapturesEnPassant:
8645       case WhitePromotionChancellor:
8646       case BlackPromotionChancellor:
8647       case WhitePromotionArchbishop:
8648       case BlackPromotionArchbishop:
8649       case WhitePromotionCentaur:
8650       case BlackPromotionCentaur:
8651       case WhitePromotionQueen:
8652       case BlackPromotionQueen:
8653       case WhitePromotionRook:
8654       case BlackPromotionRook:
8655       case WhitePromotionBishop:
8656       case BlackPromotionBishop:
8657       case WhitePromotionKnight:
8658       case BlackPromotionKnight:
8659       case WhitePromotionKing:
8660       case BlackPromotionKing:
8661       case NormalMove:
8662       case WhiteKingSideCastle:
8663       case WhiteQueenSideCastle:
8664       case BlackKingSideCastle:
8665       case BlackQueenSideCastle:
8666       case WhiteKingSideCastleWild:
8667       case WhiteQueenSideCastleWild:
8668       case BlackKingSideCastleWild:
8669       case BlackQueenSideCastleWild:
8670       /* PUSH Fabien */
8671       case WhiteHSideCastleFR:
8672       case WhiteASideCastleFR:
8673       case BlackHSideCastleFR:
8674       case BlackASideCastleFR:
8675       /* POP Fabien */
8676         if (appData.debugMode)
8677           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8678         fromX = currentMoveString[0] - AAA;
8679         fromY = currentMoveString[1] - ONE;
8680         toX = currentMoveString[2] - AAA;
8681         toY = currentMoveString[3] - ONE;
8682         promoChar = currentMoveString[4];
8683         break;
8684
8685       case WhiteDrop:
8686       case BlackDrop:
8687         if (appData.debugMode)
8688           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8689         fromX = moveType == WhiteDrop ?
8690           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8691         (int) CharToPiece(ToLower(currentMoveString[0]));
8692         fromY = DROP_RANK;
8693         toX = currentMoveString[2] - AAA;
8694         toY = currentMoveString[3] - ONE;
8695         break;
8696
8697       case WhiteWins:
8698       case BlackWins:
8699       case GameIsDrawn:
8700       case GameUnfinished:
8701         if (appData.debugMode)
8702           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8703         p = strchr(yy_text, '{');
8704         if (p == NULL) p = strchr(yy_text, '(');
8705         if (p == NULL) {
8706             p = yy_text;
8707             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8708         } else {
8709             q = strchr(p, *p == '{' ? '}' : ')');
8710             if (q != NULL) *q = NULLCHAR;
8711             p++;
8712         }
8713         GameEnds(moveType, p, GE_FILE);
8714         done = TRUE;
8715         if (cmailMsgLoaded) {
8716             ClearHighlights();
8717             flipView = WhiteOnMove(currentMove);
8718             if (moveType == GameUnfinished) flipView = !flipView;
8719             if (appData.debugMode)
8720               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8721         }
8722         break;
8723
8724       case (ChessMove) 0:       /* end of file */
8725         if (appData.debugMode)
8726           fprintf(debugFP, "Parser hit end of file\n");
8727         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8728           case MT_NONE:
8729           case MT_CHECK:
8730             break;
8731           case MT_CHECKMATE:
8732           case MT_STAINMATE:
8733             if (WhiteOnMove(currentMove)) {
8734                 GameEnds(BlackWins, "Black mates", GE_FILE);
8735             } else {
8736                 GameEnds(WhiteWins, "White mates", GE_FILE);
8737             }
8738             break;
8739           case MT_STALEMATE:
8740             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8741             break;
8742         }
8743         done = TRUE;
8744         break;
8745
8746       case MoveNumberOne:
8747         if (lastLoadGameStart == GNUChessGame) {
8748             /* GNUChessGames have numbers, but they aren't move numbers */
8749             if (appData.debugMode)
8750               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8751                       yy_text, (int) moveType);
8752             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8753         }
8754         /* else fall thru */
8755
8756       case XBoardGame:
8757       case GNUChessGame:
8758       case PGNTag:
8759         /* Reached start of next game in file */
8760         if (appData.debugMode)
8761           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8762         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8763           case MT_NONE:
8764           case MT_CHECK:
8765             break;
8766           case MT_CHECKMATE:
8767           case MT_STAINMATE:
8768             if (WhiteOnMove(currentMove)) {
8769                 GameEnds(BlackWins, "Black mates", GE_FILE);
8770             } else {
8771                 GameEnds(WhiteWins, "White mates", GE_FILE);
8772             }
8773             break;
8774           case MT_STALEMATE:
8775             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8776             break;
8777         }
8778         done = TRUE;
8779         break;
8780
8781       case PositionDiagram:     /* should not happen; ignore */
8782       case ElapsedTime:         /* ignore */
8783       case NAG:                 /* ignore */
8784         if (appData.debugMode)
8785           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8786                   yy_text, (int) moveType);
8787         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8788
8789       case IllegalMove:
8790         if (appData.testLegality) {
8791             if (appData.debugMode)
8792               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8793             sprintf(move, _("Illegal move: %d.%s%s"),
8794                     (forwardMostMove / 2) + 1,
8795                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8796             DisplayError(move, 0);
8797             done = TRUE;
8798         } else {
8799             if (appData.debugMode)
8800               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8801                       yy_text, currentMoveString);
8802             fromX = currentMoveString[0] - AAA;
8803             fromY = currentMoveString[1] - ONE;
8804             toX = currentMoveString[2] - AAA;
8805             toY = currentMoveString[3] - ONE;
8806             promoChar = currentMoveString[4];
8807         }
8808         break;
8809
8810       case AmbiguousMove:
8811         if (appData.debugMode)
8812           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8813         sprintf(move, _("Ambiguous move: %d.%s%s"),
8814                 (forwardMostMove / 2) + 1,
8815                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8816         DisplayError(move, 0);
8817         done = TRUE;
8818         break;
8819
8820       default:
8821       case ImpossibleMove:
8822         if (appData.debugMode)
8823           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8824         sprintf(move, _("Illegal move: %d.%s%s"),
8825                 (forwardMostMove / 2) + 1,
8826                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8827         DisplayError(move, 0);
8828         done = TRUE;
8829         break;
8830     }
8831
8832     if (done) {
8833         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8834             DrawPosition(FALSE, boards[currentMove]);
8835             DisplayBothClocks();
8836             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8837               DisplayComment(currentMove - 1, commentList[currentMove]);
8838         }
8839         (void) StopLoadGameTimer();
8840         gameFileFP = NULL;
8841         cmailOldMove = forwardMostMove;
8842         return FALSE;
8843     } else {
8844         /* currentMoveString is set as a side-effect of yylex */
8845         strcat(currentMoveString, "\n");
8846         strcpy(moveList[forwardMostMove], currentMoveString);
8847         
8848         thinkOutput[0] = NULLCHAR;
8849         MakeMove(fromX, fromY, toX, toY, promoChar);
8850         currentMove = forwardMostMove;
8851         return TRUE;
8852     }
8853 }
8854
8855 /* Load the nth game from the given file */
8856 int
8857 LoadGameFromFile(filename, n, title, useList)
8858      char *filename;
8859      int n;
8860      char *title;
8861      /*Boolean*/ int useList;
8862 {
8863     FILE *f;
8864     char buf[MSG_SIZ];
8865
8866     if (strcmp(filename, "-") == 0) {
8867         f = stdin;
8868         title = "stdin";
8869     } else {
8870         f = fopen(filename, "rb");
8871         if (f == NULL) {
8872           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8873             DisplayError(buf, errno);
8874             return FALSE;
8875         }
8876     }
8877     if (fseek(f, 0, 0) == -1) {
8878         /* f is not seekable; probably a pipe */
8879         useList = FALSE;
8880     }
8881     if (useList && n == 0) {
8882         int error = GameListBuild(f);
8883         if (error) {
8884             DisplayError(_("Cannot build game list"), error);
8885         } else if (!ListEmpty(&gameList) &&
8886                    ((ListGame *) gameList.tailPred)->number > 1) {
8887             GameListPopUp(f, title);
8888             return TRUE;
8889         }
8890         GameListDestroy();
8891         n = 1;
8892     }
8893     if (n == 0) n = 1;
8894     return LoadGame(f, n, title, FALSE);
8895 }
8896
8897
8898 void
8899 MakeRegisteredMove()
8900 {
8901     int fromX, fromY, toX, toY;
8902     char promoChar;
8903     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8904         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8905           case CMAIL_MOVE:
8906           case CMAIL_DRAW:
8907             if (appData.debugMode)
8908               fprintf(debugFP, "Restoring %s for game %d\n",
8909                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8910     
8911             thinkOutput[0] = NULLCHAR;
8912             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8913             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8914             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8915             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8916             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8917             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8918             MakeMove(fromX, fromY, toX, toY, promoChar);
8919             ShowMove(fromX, fromY, toX, toY);
8920               
8921             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8922               case MT_NONE:
8923               case MT_CHECK:
8924                 break;
8925                 
8926               case MT_CHECKMATE:
8927               case MT_STAINMATE:
8928                 if (WhiteOnMove(currentMove)) {
8929                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8930                 } else {
8931                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8932                 }
8933                 break;
8934                 
8935               case MT_STALEMATE:
8936                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8937                 break;
8938             }
8939
8940             break;
8941             
8942           case CMAIL_RESIGN:
8943             if (WhiteOnMove(currentMove)) {
8944                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8945             } else {
8946                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8947             }
8948             break;
8949             
8950           case CMAIL_ACCEPT:
8951             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8952             break;
8953               
8954           default:
8955             break;
8956         }
8957     }
8958
8959     return;
8960 }
8961
8962 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8963 int
8964 CmailLoadGame(f, gameNumber, title, useList)
8965      FILE *f;
8966      int gameNumber;
8967      char *title;
8968      int useList;
8969 {
8970     int retVal;
8971
8972     if (gameNumber > nCmailGames) {
8973         DisplayError(_("No more games in this message"), 0);
8974         return FALSE;
8975     }
8976     if (f == lastLoadGameFP) {
8977         int offset = gameNumber - lastLoadGameNumber;
8978         if (offset == 0) {
8979             cmailMsg[0] = NULLCHAR;
8980             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8981                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8982                 nCmailMovesRegistered--;
8983             }
8984             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8985             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8986                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8987             }
8988         } else {
8989             if (! RegisterMove()) return FALSE;
8990         }
8991     }
8992
8993     retVal = LoadGame(f, gameNumber, title, useList);
8994
8995     /* Make move registered during previous look at this game, if any */
8996     MakeRegisteredMove();
8997
8998     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8999         commentList[currentMove]
9000           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9001         DisplayComment(currentMove - 1, commentList[currentMove]);
9002     }
9003
9004     return retVal;
9005 }
9006
9007 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9008 int
9009 ReloadGame(offset)
9010      int offset;
9011 {
9012     int gameNumber = lastLoadGameNumber + offset;
9013     if (lastLoadGameFP == NULL) {
9014         DisplayError(_("No game has been loaded yet"), 0);
9015         return FALSE;
9016     }
9017     if (gameNumber <= 0) {
9018         DisplayError(_("Can't back up any further"), 0);
9019         return FALSE;
9020     }
9021     if (cmailMsgLoaded) {
9022         return CmailLoadGame(lastLoadGameFP, gameNumber,
9023                              lastLoadGameTitle, lastLoadGameUseList);
9024     } else {
9025         return LoadGame(lastLoadGameFP, gameNumber,
9026                         lastLoadGameTitle, lastLoadGameUseList);
9027     }
9028 }
9029
9030
9031
9032 /* Load the nth game from open file f */
9033 int
9034 LoadGame(f, gameNumber, title, useList)
9035      FILE *f;
9036      int gameNumber;
9037      char *title;
9038      int useList;
9039 {
9040     ChessMove cm;
9041     char buf[MSG_SIZ];
9042     int gn = gameNumber;
9043     ListGame *lg = NULL;
9044     int numPGNTags = 0;
9045     int err;
9046     GameMode oldGameMode;
9047     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9048
9049     if (appData.debugMode) 
9050         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9051
9052     if (gameMode == Training )
9053         SetTrainingModeOff();
9054
9055     oldGameMode = gameMode;
9056     if (gameMode != BeginningOfGame) {
9057       Reset(FALSE, TRUE);
9058     }
9059
9060     gameFileFP = f;
9061     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9062         fclose(lastLoadGameFP);
9063     }
9064
9065     if (useList) {
9066         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9067         
9068         if (lg) {
9069             fseek(f, lg->offset, 0);
9070             GameListHighlight(gameNumber);
9071             gn = 1;
9072         }
9073         else {
9074             DisplayError(_("Game number out of range"), 0);
9075             return FALSE;
9076         }
9077     } else {
9078         GameListDestroy();
9079         if (fseek(f, 0, 0) == -1) {
9080             if (f == lastLoadGameFP ?
9081                 gameNumber == lastLoadGameNumber + 1 :
9082                 gameNumber == 1) {
9083                 gn = 1;
9084             } else {
9085                 DisplayError(_("Can't seek on game file"), 0);
9086                 return FALSE;
9087             }
9088         }
9089     }
9090     lastLoadGameFP = f;
9091     lastLoadGameNumber = gameNumber;
9092     strcpy(lastLoadGameTitle, title);
9093     lastLoadGameUseList = useList;
9094
9095     yynewfile(f);
9096
9097     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9098       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9099                 lg->gameInfo.black);
9100             DisplayTitle(buf);
9101     } else if (*title != NULLCHAR) {
9102         if (gameNumber > 1) {
9103             sprintf(buf, "%s %d", title, gameNumber);
9104             DisplayTitle(buf);
9105         } else {
9106             DisplayTitle(title);
9107         }
9108     }
9109
9110     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9111         gameMode = PlayFromGameFile;
9112         ModeHighlight();
9113     }
9114
9115     currentMove = forwardMostMove = backwardMostMove = 0;
9116     CopyBoard(boards[0], initialPosition);
9117     StopClocks();
9118
9119     /*
9120      * Skip the first gn-1 games in the file.
9121      * Also skip over anything that precedes an identifiable 
9122      * start of game marker, to avoid being confused by 
9123      * garbage at the start of the file.  Currently 
9124      * recognized start of game markers are the move number "1",
9125      * the pattern "gnuchess .* game", the pattern
9126      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9127      * A game that starts with one of the latter two patterns
9128      * will also have a move number 1, possibly
9129      * following a position diagram.
9130      * 5-4-02: Let's try being more lenient and allowing a game to
9131      * start with an unnumbered move.  Does that break anything?
9132      */
9133     cm = lastLoadGameStart = (ChessMove) 0;
9134     while (gn > 0) {
9135         yyboardindex = forwardMostMove;
9136         cm = (ChessMove) yylex();
9137         switch (cm) {
9138           case (ChessMove) 0:
9139             if (cmailMsgLoaded) {
9140                 nCmailGames = CMAIL_MAX_GAMES - gn;
9141             } else {
9142                 Reset(TRUE, TRUE);
9143                 DisplayError(_("Game not found in file"), 0);
9144             }
9145             return FALSE;
9146
9147           case GNUChessGame:
9148           case XBoardGame:
9149             gn--;
9150             lastLoadGameStart = cm;
9151             break;
9152             
9153           case MoveNumberOne:
9154             switch (lastLoadGameStart) {
9155               case GNUChessGame:
9156               case XBoardGame:
9157               case PGNTag:
9158                 break;
9159               case MoveNumberOne:
9160               case (ChessMove) 0:
9161                 gn--;           /* count this game */
9162                 lastLoadGameStart = cm;
9163                 break;
9164               default:
9165                 /* impossible */
9166                 break;
9167             }
9168             break;
9169
9170           case PGNTag:
9171             switch (lastLoadGameStart) {
9172               case GNUChessGame:
9173               case PGNTag:
9174               case MoveNumberOne:
9175               case (ChessMove) 0:
9176                 gn--;           /* count this game */
9177                 lastLoadGameStart = cm;
9178                 break;
9179               case XBoardGame:
9180                 lastLoadGameStart = cm; /* game counted already */
9181                 break;
9182               default:
9183                 /* impossible */
9184                 break;
9185             }
9186             if (gn > 0) {
9187                 do {
9188                     yyboardindex = forwardMostMove;
9189                     cm = (ChessMove) yylex();
9190                 } while (cm == PGNTag || cm == Comment);
9191             }
9192             break;
9193
9194           case WhiteWins:
9195           case BlackWins:
9196           case GameIsDrawn:
9197             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9198                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9199                     != CMAIL_OLD_RESULT) {
9200                     nCmailResults ++ ;
9201                     cmailResult[  CMAIL_MAX_GAMES
9202                                 - gn - 1] = CMAIL_OLD_RESULT;
9203                 }
9204             }
9205             break;
9206
9207           case NormalMove:
9208             /* Only a NormalMove can be at the start of a game
9209              * without a position diagram. */
9210             if (lastLoadGameStart == (ChessMove) 0) {
9211               gn--;
9212               lastLoadGameStart = MoveNumberOne;
9213             }
9214             break;
9215
9216           default:
9217             break;
9218         }
9219     }
9220     
9221     if (appData.debugMode)
9222       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9223
9224     if (cm == XBoardGame) {
9225         /* Skip any header junk before position diagram and/or move 1 */
9226         for (;;) {
9227             yyboardindex = forwardMostMove;
9228             cm = (ChessMove) yylex();
9229
9230             if (cm == (ChessMove) 0 ||
9231                 cm == GNUChessGame || cm == XBoardGame) {
9232                 /* Empty game; pretend end-of-file and handle later */
9233                 cm = (ChessMove) 0;
9234                 break;
9235             }
9236
9237             if (cm == MoveNumberOne || cm == PositionDiagram ||
9238                 cm == PGNTag || cm == Comment)
9239               break;
9240         }
9241     } else if (cm == GNUChessGame) {
9242         if (gameInfo.event != NULL) {
9243             free(gameInfo.event);
9244         }
9245         gameInfo.event = StrSave(yy_text);
9246     }   
9247
9248     startedFromSetupPosition = FALSE;
9249     while (cm == PGNTag) {
9250         if (appData.debugMode) 
9251           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9252         err = ParsePGNTag(yy_text, &gameInfo);
9253         if (!err) numPGNTags++;
9254
9255         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9256         if(gameInfo.variant != oldVariant) {
9257             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9258             InitPosition(TRUE);
9259             oldVariant = gameInfo.variant;
9260             if (appData.debugMode) 
9261               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9262         }
9263
9264
9265         if (gameInfo.fen != NULL) {
9266           Board initial_position;
9267           startedFromSetupPosition = TRUE;
9268           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9269             Reset(TRUE, TRUE);
9270             DisplayError(_("Bad FEN position in file"), 0);
9271             return FALSE;
9272           }
9273           CopyBoard(boards[0], initial_position);
9274           if (blackPlaysFirst) {
9275             currentMove = forwardMostMove = backwardMostMove = 1;
9276             CopyBoard(boards[1], initial_position);
9277             strcpy(moveList[0], "");
9278             strcpy(parseList[0], "");
9279             timeRemaining[0][1] = whiteTimeRemaining;
9280             timeRemaining[1][1] = blackTimeRemaining;
9281             if (commentList[0] != NULL) {
9282               commentList[1] = commentList[0];
9283               commentList[0] = NULL;
9284             }
9285           } else {
9286             currentMove = forwardMostMove = backwardMostMove = 0;
9287           }
9288           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9289           {   int i;
9290               initialRulePlies = FENrulePlies;
9291               for( i=0; i< nrCastlingRights; i++ )
9292                   initialRights[i] = initial_position[CASTLING][i];
9293           }
9294           yyboardindex = forwardMostMove;
9295           free(gameInfo.fen);
9296           gameInfo.fen = NULL;
9297         }
9298
9299         yyboardindex = forwardMostMove;
9300         cm = (ChessMove) yylex();
9301
9302         /* Handle comments interspersed among the tags */
9303         while (cm == Comment) {
9304             char *p;
9305             if (appData.debugMode) 
9306               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9307             p = yy_text;
9308             AppendComment(currentMove, p, FALSE);
9309             yyboardindex = forwardMostMove;
9310             cm = (ChessMove) yylex();
9311         }
9312     }
9313
9314     /* don't rely on existence of Event tag since if game was
9315      * pasted from clipboard the Event tag may not exist
9316      */
9317     if (numPGNTags > 0){
9318         char *tags;
9319         if (gameInfo.variant == VariantNormal) {
9320           gameInfo.variant = StringToVariant(gameInfo.event);
9321         }
9322         if (!matchMode) {
9323           if( appData.autoDisplayTags ) {
9324             tags = PGNTags(&gameInfo);
9325             TagsPopUp(tags, CmailMsg());
9326             free(tags);
9327           }
9328         }
9329     } else {
9330         /* Make something up, but don't display it now */
9331         SetGameInfo();
9332         TagsPopDown();
9333     }
9334
9335     if (cm == PositionDiagram) {
9336         int i, j;
9337         char *p;
9338         Board initial_position;
9339
9340         if (appData.debugMode)
9341           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9342
9343         if (!startedFromSetupPosition) {
9344             p = yy_text;
9345             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9346               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9347                 switch (*p) {
9348                   case '[':
9349                   case '-':
9350                   case ' ':
9351                   case '\t':
9352                   case '\n':
9353                   case '\r':
9354                     break;
9355                   default:
9356                     initial_position[i][j++] = CharToPiece(*p);
9357                     break;
9358                 }
9359             while (*p == ' ' || *p == '\t' ||
9360                    *p == '\n' || *p == '\r') p++;
9361         
9362             if (strncmp(p, "black", strlen("black"))==0)
9363               blackPlaysFirst = TRUE;
9364             else
9365               blackPlaysFirst = FALSE;
9366             startedFromSetupPosition = TRUE;
9367         
9368             CopyBoard(boards[0], initial_position);
9369             if (blackPlaysFirst) {
9370                 currentMove = forwardMostMove = backwardMostMove = 1;
9371                 CopyBoard(boards[1], initial_position);
9372                 strcpy(moveList[0], "");
9373                 strcpy(parseList[0], "");
9374                 timeRemaining[0][1] = whiteTimeRemaining;
9375                 timeRemaining[1][1] = blackTimeRemaining;
9376                 if (commentList[0] != NULL) {
9377                     commentList[1] = commentList[0];
9378                     commentList[0] = NULL;
9379                 }
9380             } else {
9381                 currentMove = forwardMostMove = backwardMostMove = 0;
9382             }
9383         }
9384         yyboardindex = forwardMostMove;
9385         cm = (ChessMove) yylex();
9386     }
9387
9388     if (first.pr == NoProc) {
9389         StartChessProgram(&first);
9390     }
9391     InitChessProgram(&first, FALSE);
9392     SendToProgram("force\n", &first);
9393     if (startedFromSetupPosition) {
9394         SendBoard(&first, forwardMostMove);
9395     if (appData.debugMode) {
9396         fprintf(debugFP, "Load Game\n");
9397     }
9398         DisplayBothClocks();
9399     }      
9400
9401     /* [HGM] server: flag to write setup moves in broadcast file as one */
9402     loadFlag = appData.suppressLoadMoves;
9403
9404     while (cm == Comment) {
9405         char *p;
9406         if (appData.debugMode) 
9407           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9408         p = yy_text;
9409         AppendComment(currentMove, p, FALSE);
9410         yyboardindex = forwardMostMove;
9411         cm = (ChessMove) yylex();
9412     }
9413
9414     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9415         cm == WhiteWins || cm == BlackWins ||
9416         cm == GameIsDrawn || cm == GameUnfinished) {
9417         DisplayMessage("", _("No moves in game"));
9418         if (cmailMsgLoaded) {
9419             if (appData.debugMode)
9420               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9421             ClearHighlights();
9422             flipView = FALSE;
9423         }
9424         DrawPosition(FALSE, boards[currentMove]);
9425         DisplayBothClocks();
9426         gameMode = EditGame;
9427         ModeHighlight();
9428         gameFileFP = NULL;
9429         cmailOldMove = 0;
9430         return TRUE;
9431     }
9432
9433     // [HGM] PV info: routine tests if comment empty
9434     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9435         DisplayComment(currentMove - 1, commentList[currentMove]);
9436     }
9437     if (!matchMode && appData.timeDelay != 0) 
9438       DrawPosition(FALSE, boards[currentMove]);
9439
9440     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9441       programStats.ok_to_send = 1;
9442     }
9443
9444     /* if the first token after the PGN tags is a move
9445      * and not move number 1, retrieve it from the parser 
9446      */
9447     if (cm != MoveNumberOne)
9448         LoadGameOneMove(cm);
9449
9450     /* load the remaining moves from the file */
9451     while (LoadGameOneMove((ChessMove)0)) {
9452       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9453       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9454     }
9455
9456     /* rewind to the start of the game */
9457     currentMove = backwardMostMove;
9458
9459     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9460
9461     if (oldGameMode == AnalyzeFile ||
9462         oldGameMode == AnalyzeMode) {
9463       AnalyzeFileEvent();
9464     }
9465
9466     if (matchMode || appData.timeDelay == 0) {
9467       ToEndEvent();
9468       gameMode = EditGame;
9469       ModeHighlight();
9470     } else if (appData.timeDelay > 0) {
9471       AutoPlayGameLoop();
9472     }
9473
9474     if (appData.debugMode) 
9475         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9476
9477     loadFlag = 0; /* [HGM] true game starts */
9478     return TRUE;
9479 }
9480
9481 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9482 int
9483 ReloadPosition(offset)
9484      int offset;
9485 {
9486     int positionNumber = lastLoadPositionNumber + offset;
9487     if (lastLoadPositionFP == NULL) {
9488         DisplayError(_("No position has been loaded yet"), 0);
9489         return FALSE;
9490     }
9491     if (positionNumber <= 0) {
9492         DisplayError(_("Can't back up any further"), 0);
9493         return FALSE;
9494     }
9495     return LoadPosition(lastLoadPositionFP, positionNumber,
9496                         lastLoadPositionTitle);
9497 }
9498
9499 /* Load the nth position from the given file */
9500 int
9501 LoadPositionFromFile(filename, n, title)
9502      char *filename;
9503      int n;
9504      char *title;
9505 {
9506     FILE *f;
9507     char buf[MSG_SIZ];
9508
9509     if (strcmp(filename, "-") == 0) {
9510         return LoadPosition(stdin, n, "stdin");
9511     } else {
9512         f = fopen(filename, "rb");
9513         if (f == NULL) {
9514             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9515             DisplayError(buf, errno);
9516             return FALSE;
9517         } else {
9518             return LoadPosition(f, n, title);
9519         }
9520     }
9521 }
9522
9523 /* Load the nth position from the given open file, and close it */
9524 int
9525 LoadPosition(f, positionNumber, title)
9526      FILE *f;
9527      int positionNumber;
9528      char *title;
9529 {
9530     char *p, line[MSG_SIZ];
9531     Board initial_position;
9532     int i, j, fenMode, pn;
9533     
9534     if (gameMode == Training )
9535         SetTrainingModeOff();
9536
9537     if (gameMode != BeginningOfGame) {
9538         Reset(FALSE, TRUE);
9539     }
9540     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9541         fclose(lastLoadPositionFP);
9542     }
9543     if (positionNumber == 0) positionNumber = 1;
9544     lastLoadPositionFP = f;
9545     lastLoadPositionNumber = positionNumber;
9546     strcpy(lastLoadPositionTitle, title);
9547     if (first.pr == NoProc) {
9548       StartChessProgram(&first);
9549       InitChessProgram(&first, FALSE);
9550     }    
9551     pn = positionNumber;
9552     if (positionNumber < 0) {
9553         /* Negative position number means to seek to that byte offset */
9554         if (fseek(f, -positionNumber, 0) == -1) {
9555             DisplayError(_("Can't seek on position file"), 0);
9556             return FALSE;
9557         };
9558         pn = 1;
9559     } else {
9560         if (fseek(f, 0, 0) == -1) {
9561             if (f == lastLoadPositionFP ?
9562                 positionNumber == lastLoadPositionNumber + 1 :
9563                 positionNumber == 1) {
9564                 pn = 1;
9565             } else {
9566                 DisplayError(_("Can't seek on position file"), 0);
9567                 return FALSE;
9568             }
9569         }
9570     }
9571     /* See if this file is FEN or old-style xboard */
9572     if (fgets(line, MSG_SIZ, f) == NULL) {
9573         DisplayError(_("Position not found in file"), 0);
9574         return FALSE;
9575     }
9576     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9577     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9578
9579     if (pn >= 2) {
9580         if (fenMode || line[0] == '#') pn--;
9581         while (pn > 0) {
9582             /* skip positions before number pn */
9583             if (fgets(line, MSG_SIZ, f) == NULL) {
9584                 Reset(TRUE, TRUE);
9585                 DisplayError(_("Position not found in file"), 0);
9586                 return FALSE;
9587             }
9588             if (fenMode || line[0] == '#') pn--;
9589         }
9590     }
9591
9592     if (fenMode) {
9593         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9594             DisplayError(_("Bad FEN position in file"), 0);
9595             return FALSE;
9596         }
9597     } else {
9598         (void) fgets(line, MSG_SIZ, f);
9599         (void) fgets(line, MSG_SIZ, f);
9600     
9601         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9602             (void) fgets(line, MSG_SIZ, f);
9603             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9604                 if (*p == ' ')
9605                   continue;
9606                 initial_position[i][j++] = CharToPiece(*p);
9607             }
9608         }
9609     
9610         blackPlaysFirst = FALSE;
9611         if (!feof(f)) {
9612             (void) fgets(line, MSG_SIZ, f);
9613             if (strncmp(line, "black", strlen("black"))==0)
9614               blackPlaysFirst = TRUE;
9615         }
9616     }
9617     startedFromSetupPosition = TRUE;
9618     
9619     SendToProgram("force\n", &first);
9620     CopyBoard(boards[0], initial_position);
9621     if (blackPlaysFirst) {
9622         currentMove = forwardMostMove = backwardMostMove = 1;
9623         strcpy(moveList[0], "");
9624         strcpy(parseList[0], "");
9625         CopyBoard(boards[1], initial_position);
9626         DisplayMessage("", _("Black to play"));
9627     } else {
9628         currentMove = forwardMostMove = backwardMostMove = 0;
9629         DisplayMessage("", _("White to play"));
9630     }
9631     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9632     SendBoard(&first, forwardMostMove);
9633     if (appData.debugMode) {
9634 int i, j;
9635   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9636   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9637         fprintf(debugFP, "Load Position\n");
9638     }
9639
9640     if (positionNumber > 1) {
9641         sprintf(line, "%s %d", title, positionNumber);
9642         DisplayTitle(line);
9643     } else {
9644         DisplayTitle(title);
9645     }
9646     gameMode = EditGame;
9647     ModeHighlight();
9648     ResetClocks();
9649     timeRemaining[0][1] = whiteTimeRemaining;
9650     timeRemaining[1][1] = blackTimeRemaining;
9651     DrawPosition(FALSE, boards[currentMove]);
9652    
9653     return TRUE;
9654 }
9655
9656
9657 void
9658 CopyPlayerNameIntoFileName(dest, src)
9659      char **dest, *src;
9660 {
9661     while (*src != NULLCHAR && *src != ',') {
9662         if (*src == ' ') {
9663             *(*dest)++ = '_';
9664             src++;
9665         } else {
9666             *(*dest)++ = *src++;
9667         }
9668     }
9669 }
9670
9671 char *DefaultFileName(ext)
9672      char *ext;
9673 {
9674     static char def[MSG_SIZ];
9675     char *p;
9676
9677     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9678         p = def;
9679         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9680         *p++ = '-';
9681         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9682         *p++ = '.';
9683         strcpy(p, ext);
9684     } else {
9685         def[0] = NULLCHAR;
9686     }
9687     return def;
9688 }
9689
9690 /* Save the current game to the given file */
9691 int
9692 SaveGameToFile(filename, append)
9693      char *filename;
9694      int append;
9695 {
9696     FILE *f;
9697     char buf[MSG_SIZ];
9698
9699     if (strcmp(filename, "-") == 0) {
9700         return SaveGame(stdout, 0, NULL);
9701     } else {
9702         f = fopen(filename, append ? "a" : "w");
9703         if (f == NULL) {
9704             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9705             DisplayError(buf, errno);
9706             return FALSE;
9707         } else {
9708             return SaveGame(f, 0, NULL);
9709         }
9710     }
9711 }
9712
9713 char *
9714 SavePart(str)
9715      char *str;
9716 {
9717     static char buf[MSG_SIZ];
9718     char *p;
9719     
9720     p = strchr(str, ' ');
9721     if (p == NULL) return str;
9722     strncpy(buf, str, p - str);
9723     buf[p - str] = NULLCHAR;
9724     return buf;
9725 }
9726
9727 #define PGN_MAX_LINE 75
9728
9729 #define PGN_SIDE_WHITE  0
9730 #define PGN_SIDE_BLACK  1
9731
9732 /* [AS] */
9733 static int FindFirstMoveOutOfBook( int side )
9734 {
9735     int result = -1;
9736
9737     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9738         int index = backwardMostMove;
9739         int has_book_hit = 0;
9740
9741         if( (index % 2) != side ) {
9742             index++;
9743         }
9744
9745         while( index < forwardMostMove ) {
9746             /* Check to see if engine is in book */
9747             int depth = pvInfoList[index].depth;
9748             int score = pvInfoList[index].score;
9749             int in_book = 0;
9750
9751             if( depth <= 2 ) {
9752                 in_book = 1;
9753             }
9754             else if( score == 0 && depth == 63 ) {
9755                 in_book = 1; /* Zappa */
9756             }
9757             else if( score == 2 && depth == 99 ) {
9758                 in_book = 1; /* Abrok */
9759             }
9760
9761             has_book_hit += in_book;
9762
9763             if( ! in_book ) {
9764                 result = index;
9765
9766                 break;
9767             }
9768
9769             index += 2;
9770         }
9771     }
9772
9773     return result;
9774 }
9775
9776 /* [AS] */
9777 void GetOutOfBookInfo( char * buf )
9778 {
9779     int oob[2];
9780     int i;
9781     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9782
9783     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9784     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9785
9786     *buf = '\0';
9787
9788     if( oob[0] >= 0 || oob[1] >= 0 ) {
9789         for( i=0; i<2; i++ ) {
9790             int idx = oob[i];
9791
9792             if( idx >= 0 ) {
9793                 if( i > 0 && oob[0] >= 0 ) {
9794                     strcat( buf, "   " );
9795                 }
9796
9797                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9798                 sprintf( buf+strlen(buf), "%s%.2f", 
9799                     pvInfoList[idx].score >= 0 ? "+" : "",
9800                     pvInfoList[idx].score / 100.0 );
9801             }
9802         }
9803     }
9804 }
9805
9806 /* Save game in PGN style and close the file */
9807 int
9808 SaveGamePGN(f)
9809      FILE *f;
9810 {
9811     int i, offset, linelen, newblock;
9812     time_t tm;
9813 //    char *movetext;
9814     char numtext[32];
9815     int movelen, numlen, blank;
9816     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9817
9818     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9819     
9820     tm = time((time_t *) NULL);
9821     
9822     PrintPGNTags(f, &gameInfo);
9823     
9824     if (backwardMostMove > 0 || startedFromSetupPosition) {
9825         char *fen = PositionToFEN(backwardMostMove, NULL);
9826         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9827         fprintf(f, "\n{--------------\n");
9828         PrintPosition(f, backwardMostMove);
9829         fprintf(f, "--------------}\n");
9830         free(fen);
9831     }
9832     else {
9833         /* [AS] Out of book annotation */
9834         if( appData.saveOutOfBookInfo ) {
9835             char buf[64];
9836
9837             GetOutOfBookInfo( buf );
9838
9839             if( buf[0] != '\0' ) {
9840                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9841             }
9842         }
9843
9844         fprintf(f, "\n");
9845     }
9846
9847     i = backwardMostMove;
9848     linelen = 0;
9849     newblock = TRUE;
9850
9851     while (i < forwardMostMove) {
9852         /* Print comments preceding this move */
9853         if (commentList[i] != NULL) {
9854             if (linelen > 0) fprintf(f, "\n");
9855             fprintf(f, "%s\n", commentList[i]);
9856             linelen = 0;
9857             newblock = TRUE;
9858         }
9859
9860         /* Format move number */
9861         if ((i % 2) == 0) {
9862             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9863         } else {
9864             if (newblock) {
9865                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9866             } else {
9867                 numtext[0] = NULLCHAR;
9868             }
9869         }
9870         numlen = strlen(numtext);
9871         newblock = FALSE;
9872
9873         /* Print move number */
9874         blank = linelen > 0 && numlen > 0;
9875         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9876             fprintf(f, "\n");
9877             linelen = 0;
9878             blank = 0;
9879         }
9880         if (blank) {
9881             fprintf(f, " ");
9882             linelen++;
9883         }
9884         fprintf(f, "%s", numtext);
9885         linelen += numlen;
9886
9887         /* Get move */
9888         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9889         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9890
9891         /* Print move */
9892         blank = linelen > 0 && movelen > 0;
9893         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9894             fprintf(f, "\n");
9895             linelen = 0;
9896             blank = 0;
9897         }
9898         if (blank) {
9899             fprintf(f, " ");
9900             linelen++;
9901         }
9902         fprintf(f, "%s", move_buffer);
9903         linelen += movelen;
9904
9905         /* [AS] Add PV info if present */
9906         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9907             /* [HGM] add time */
9908             char buf[MSG_SIZ]; int seconds = 0;
9909
9910             if(i >= backwardMostMove) {
9911                 if(WhiteOnMove(i))
9912                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9913                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9914                 else
9915                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9916                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9917             }
9918             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9919
9920             if( seconds <= 0) buf[0] = 0; else
9921             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9922                 seconds = (seconds + 4)/10; // round to full seconds
9923                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9924                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9925             }
9926
9927             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9928                 pvInfoList[i].score >= 0 ? "+" : "",
9929                 pvInfoList[i].score / 100.0,
9930                 pvInfoList[i].depth,
9931                 buf );
9932
9933             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9934
9935             /* Print score/depth */
9936             blank = linelen > 0 && movelen > 0;
9937             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9938                 fprintf(f, "\n");
9939                 linelen = 0;
9940                 blank = 0;
9941             }
9942             if (blank) {
9943                 fprintf(f, " ");
9944                 linelen++;
9945             }
9946             fprintf(f, "%s", move_buffer);
9947             linelen += movelen;
9948         }
9949
9950         i++;
9951     }
9952     
9953     /* Start a new line */
9954     if (linelen > 0) fprintf(f, "\n");
9955
9956     /* Print comments after last move */
9957     if (commentList[i] != NULL) {
9958         fprintf(f, "%s\n", commentList[i]);
9959     }
9960
9961     /* Print result */
9962     if (gameInfo.resultDetails != NULL &&
9963         gameInfo.resultDetails[0] != NULLCHAR) {
9964         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9965                 PGNResult(gameInfo.result));
9966     } else {
9967         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9968     }
9969
9970     fclose(f);
9971     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9972     return TRUE;
9973 }
9974
9975 /* Save game in old style and close the file */
9976 int
9977 SaveGameOldStyle(f)
9978      FILE *f;
9979 {
9980     int i, offset;
9981     time_t tm;
9982     
9983     tm = time((time_t *) NULL);
9984     
9985     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9986     PrintOpponents(f);
9987     
9988     if (backwardMostMove > 0 || startedFromSetupPosition) {
9989         fprintf(f, "\n[--------------\n");
9990         PrintPosition(f, backwardMostMove);
9991         fprintf(f, "--------------]\n");
9992     } else {
9993         fprintf(f, "\n");
9994     }
9995
9996     i = backwardMostMove;
9997     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9998
9999     while (i < forwardMostMove) {
10000         if (commentList[i] != NULL) {
10001             fprintf(f, "[%s]\n", commentList[i]);
10002         }
10003
10004         if ((i % 2) == 1) {
10005             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10006             i++;
10007         } else {
10008             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10009             i++;
10010             if (commentList[i] != NULL) {
10011                 fprintf(f, "\n");
10012                 continue;
10013             }
10014             if (i >= forwardMostMove) {
10015                 fprintf(f, "\n");
10016                 break;
10017             }
10018             fprintf(f, "%s\n", parseList[i]);
10019             i++;
10020         }
10021     }
10022     
10023     if (commentList[i] != NULL) {
10024         fprintf(f, "[%s]\n", commentList[i]);
10025     }
10026
10027     /* This isn't really the old style, but it's close enough */
10028     if (gameInfo.resultDetails != NULL &&
10029         gameInfo.resultDetails[0] != NULLCHAR) {
10030         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10031                 gameInfo.resultDetails);
10032     } else {
10033         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10034     }
10035
10036     fclose(f);
10037     return TRUE;
10038 }
10039
10040 /* Save the current game to open file f and close the file */
10041 int
10042 SaveGame(f, dummy, dummy2)
10043      FILE *f;
10044      int dummy;
10045      char *dummy2;
10046 {
10047     if (gameMode == EditPosition) EditPositionDone(TRUE);
10048     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10049     if (appData.oldSaveStyle)
10050       return SaveGameOldStyle(f);
10051     else
10052       return SaveGamePGN(f);
10053 }
10054
10055 /* Save the current position to the given file */
10056 int
10057 SavePositionToFile(filename)
10058      char *filename;
10059 {
10060     FILE *f;
10061     char buf[MSG_SIZ];
10062
10063     if (strcmp(filename, "-") == 0) {
10064         return SavePosition(stdout, 0, NULL);
10065     } else {
10066         f = fopen(filename, "a");
10067         if (f == NULL) {
10068             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10069             DisplayError(buf, errno);
10070             return FALSE;
10071         } else {
10072             SavePosition(f, 0, NULL);
10073             return TRUE;
10074         }
10075     }
10076 }
10077
10078 /* Save the current position to the given open file and close the file */
10079 int
10080 SavePosition(f, dummy, dummy2)
10081      FILE *f;
10082      int dummy;
10083      char *dummy2;
10084 {
10085     time_t tm;
10086     char *fen;
10087     
10088     if (appData.oldSaveStyle) {
10089         tm = time((time_t *) NULL);
10090     
10091         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10092         PrintOpponents(f);
10093         fprintf(f, "[--------------\n");
10094         PrintPosition(f, currentMove);
10095         fprintf(f, "--------------]\n");
10096     } else {
10097         fen = PositionToFEN(currentMove, NULL);
10098         fprintf(f, "%s\n", fen);
10099         free(fen);
10100     }
10101     fclose(f);
10102     return TRUE;
10103 }
10104
10105 void
10106 ReloadCmailMsgEvent(unregister)
10107      int unregister;
10108 {
10109 #if !WIN32
10110     static char *inFilename = NULL;
10111     static char *outFilename;
10112     int i;
10113     struct stat inbuf, outbuf;
10114     int status;
10115     
10116     /* Any registered moves are unregistered if unregister is set, */
10117     /* i.e. invoked by the signal handler */
10118     if (unregister) {
10119         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10120             cmailMoveRegistered[i] = FALSE;
10121             if (cmailCommentList[i] != NULL) {
10122                 free(cmailCommentList[i]);
10123                 cmailCommentList[i] = NULL;
10124             }
10125         }
10126         nCmailMovesRegistered = 0;
10127     }
10128
10129     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10130         cmailResult[i] = CMAIL_NOT_RESULT;
10131     }
10132     nCmailResults = 0;
10133
10134     if (inFilename == NULL) {
10135         /* Because the filenames are static they only get malloced once  */
10136         /* and they never get freed                                      */
10137         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10138         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10139
10140         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10141         sprintf(outFilename, "%s.out", appData.cmailGameName);
10142     }
10143     
10144     status = stat(outFilename, &outbuf);
10145     if (status < 0) {
10146         cmailMailedMove = FALSE;
10147     } else {
10148         status = stat(inFilename, &inbuf);
10149         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10150     }
10151     
10152     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10153        counts the games, notes how each one terminated, etc.
10154        
10155        It would be nice to remove this kludge and instead gather all
10156        the information while building the game list.  (And to keep it
10157        in the game list nodes instead of having a bunch of fixed-size
10158        parallel arrays.)  Note this will require getting each game's
10159        termination from the PGN tags, as the game list builder does
10160        not process the game moves.  --mann
10161        */
10162     cmailMsgLoaded = TRUE;
10163     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10164     
10165     /* Load first game in the file or popup game menu */
10166     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10167
10168 #endif /* !WIN32 */
10169     return;
10170 }
10171
10172 int
10173 RegisterMove()
10174 {
10175     FILE *f;
10176     char string[MSG_SIZ];
10177
10178     if (   cmailMailedMove
10179         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10180         return TRUE;            /* Allow free viewing  */
10181     }
10182
10183     /* Unregister move to ensure that we don't leave RegisterMove        */
10184     /* with the move registered when the conditions for registering no   */
10185     /* longer hold                                                       */
10186     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10187         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10188         nCmailMovesRegistered --;
10189
10190         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10191           {
10192               free(cmailCommentList[lastLoadGameNumber - 1]);
10193               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10194           }
10195     }
10196
10197     if (cmailOldMove == -1) {
10198         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10199         return FALSE;
10200     }
10201
10202     if (currentMove > cmailOldMove + 1) {
10203         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10204         return FALSE;
10205     }
10206
10207     if (currentMove < cmailOldMove) {
10208         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10209         return FALSE;
10210     }
10211
10212     if (forwardMostMove > currentMove) {
10213         /* Silently truncate extra moves */
10214         TruncateGame();
10215     }
10216
10217     if (   (currentMove == cmailOldMove + 1)
10218         || (   (currentMove == cmailOldMove)
10219             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10220                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10221         if (gameInfo.result != GameUnfinished) {
10222             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10223         }
10224
10225         if (commentList[currentMove] != NULL) {
10226             cmailCommentList[lastLoadGameNumber - 1]
10227               = StrSave(commentList[currentMove]);
10228         }
10229         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10230
10231         if (appData.debugMode)
10232           fprintf(debugFP, "Saving %s for game %d\n",
10233                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10234
10235         sprintf(string,
10236                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10237         
10238         f = fopen(string, "w");
10239         if (appData.oldSaveStyle) {
10240             SaveGameOldStyle(f); /* also closes the file */
10241             
10242             sprintf(string, "%s.pos.out", appData.cmailGameName);
10243             f = fopen(string, "w");
10244             SavePosition(f, 0, NULL); /* also closes the file */
10245         } else {
10246             fprintf(f, "{--------------\n");
10247             PrintPosition(f, currentMove);
10248             fprintf(f, "--------------}\n\n");
10249             
10250             SaveGame(f, 0, NULL); /* also closes the file*/
10251         }
10252         
10253         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10254         nCmailMovesRegistered ++;
10255     } else if (nCmailGames == 1) {
10256         DisplayError(_("You have not made a move yet"), 0);
10257         return FALSE;
10258     }
10259
10260     return TRUE;
10261 }
10262
10263 void
10264 MailMoveEvent()
10265 {
10266 #if !WIN32
10267     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10268     FILE *commandOutput;
10269     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10270     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10271     int nBuffers;
10272     int i;
10273     int archived;
10274     char *arcDir;
10275
10276     if (! cmailMsgLoaded) {
10277         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10278         return;
10279     }
10280
10281     if (nCmailGames == nCmailResults) {
10282         DisplayError(_("No unfinished games"), 0);
10283         return;
10284     }
10285
10286 #if CMAIL_PROHIBIT_REMAIL
10287     if (cmailMailedMove) {
10288         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);
10289         DisplayError(msg, 0);
10290         return;
10291     }
10292 #endif
10293
10294     if (! (cmailMailedMove || RegisterMove())) return;
10295     
10296     if (   cmailMailedMove
10297         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10298         sprintf(string, partCommandString,
10299                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10300         commandOutput = popen(string, "r");
10301
10302         if (commandOutput == NULL) {
10303             DisplayError(_("Failed to invoke cmail"), 0);
10304         } else {
10305             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10306                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10307             }
10308             if (nBuffers > 1) {
10309                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10310                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10311                 nBytes = MSG_SIZ - 1;
10312             } else {
10313                 (void) memcpy(msg, buffer, nBytes);
10314             }
10315             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10316
10317             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10318                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10319
10320                 archived = TRUE;
10321                 for (i = 0; i < nCmailGames; i ++) {
10322                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10323                         archived = FALSE;
10324                     }
10325                 }
10326                 if (   archived
10327                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10328                         != NULL)) {
10329                     sprintf(buffer, "%s/%s.%s.archive",
10330                             arcDir,
10331                             appData.cmailGameName,
10332                             gameInfo.date);
10333                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10334                     cmailMsgLoaded = FALSE;
10335                 }
10336             }
10337
10338             DisplayInformation(msg);
10339             pclose(commandOutput);
10340         }
10341     } else {
10342         if ((*cmailMsg) != '\0') {
10343             DisplayInformation(cmailMsg);
10344         }
10345     }
10346
10347     return;
10348 #endif /* !WIN32 */
10349 }
10350
10351 char *
10352 CmailMsg()
10353 {
10354 #if WIN32
10355     return NULL;
10356 #else
10357     int  prependComma = 0;
10358     char number[5];
10359     char string[MSG_SIZ];       /* Space for game-list */
10360     int  i;
10361     
10362     if (!cmailMsgLoaded) return "";
10363
10364     if (cmailMailedMove) {
10365         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10366     } else {
10367         /* Create a list of games left */
10368         sprintf(string, "[");
10369         for (i = 0; i < nCmailGames; i ++) {
10370             if (! (   cmailMoveRegistered[i]
10371                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10372                 if (prependComma) {
10373                     sprintf(number, ",%d", i + 1);
10374                 } else {
10375                     sprintf(number, "%d", i + 1);
10376                     prependComma = 1;
10377                 }
10378                 
10379                 strcat(string, number);
10380             }
10381         }
10382         strcat(string, "]");
10383
10384         if (nCmailMovesRegistered + nCmailResults == 0) {
10385             switch (nCmailGames) {
10386               case 1:
10387                 sprintf(cmailMsg,
10388                         _("Still need to make move for game\n"));
10389                 break;
10390                 
10391               case 2:
10392                 sprintf(cmailMsg,
10393                         _("Still need to make moves for both games\n"));
10394                 break;
10395                 
10396               default:
10397                 sprintf(cmailMsg,
10398                         _("Still need to make moves for all %d games\n"),
10399                         nCmailGames);
10400                 break;
10401             }
10402         } else {
10403             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10404               case 1:
10405                 sprintf(cmailMsg,
10406                         _("Still need to make a move for game %s\n"),
10407                         string);
10408                 break;
10409                 
10410               case 0:
10411                 if (nCmailResults == nCmailGames) {
10412                     sprintf(cmailMsg, _("No unfinished games\n"));
10413                 } else {
10414                     sprintf(cmailMsg, _("Ready to send mail\n"));
10415                 }
10416                 break;
10417                 
10418               default:
10419                 sprintf(cmailMsg,
10420                         _("Still need to make moves for games %s\n"),
10421                         string);
10422             }
10423         }
10424     }
10425     return cmailMsg;
10426 #endif /* WIN32 */
10427 }
10428
10429 void
10430 ResetGameEvent()
10431 {
10432     if (gameMode == Training)
10433       SetTrainingModeOff();
10434
10435     Reset(TRUE, TRUE);
10436     cmailMsgLoaded = FALSE;
10437     if (appData.icsActive) {
10438       SendToICS(ics_prefix);
10439       SendToICS("refresh\n");
10440     }
10441 }
10442
10443 void
10444 ExitEvent(status)
10445      int status;
10446 {
10447     exiting++;
10448     if (exiting > 2) {
10449       /* Give up on clean exit */
10450       exit(status);
10451     }
10452     if (exiting > 1) {
10453       /* Keep trying for clean exit */
10454       return;
10455     }
10456
10457     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10458
10459     if (telnetISR != NULL) {
10460       RemoveInputSource(telnetISR);
10461     }
10462     if (icsPR != NoProc) {
10463       DestroyChildProcess(icsPR, TRUE);
10464     }
10465
10466     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10467     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10468
10469     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10470     /* make sure this other one finishes before killing it!                  */
10471     if(endingGame) { int count = 0;
10472         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10473         while(endingGame && count++ < 10) DoSleep(1);
10474         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10475     }
10476
10477     /* Kill off chess programs */
10478     if (first.pr != NoProc) {
10479         ExitAnalyzeMode();
10480         
10481         DoSleep( appData.delayBeforeQuit );
10482         SendToProgram("quit\n", &first);
10483         DoSleep( appData.delayAfterQuit );
10484         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10485     }
10486     if (second.pr != NoProc) {
10487         DoSleep( appData.delayBeforeQuit );
10488         SendToProgram("quit\n", &second);
10489         DoSleep( appData.delayAfterQuit );
10490         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10491     }
10492     if (first.isr != NULL) {
10493         RemoveInputSource(first.isr);
10494     }
10495     if (second.isr != NULL) {
10496         RemoveInputSource(second.isr);
10497     }
10498
10499     ShutDownFrontEnd();
10500     exit(status);
10501 }
10502
10503 void
10504 PauseEvent()
10505 {
10506     if (appData.debugMode)
10507         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10508     if (pausing) {
10509         pausing = FALSE;
10510         ModeHighlight();
10511         if (gameMode == MachinePlaysWhite ||
10512             gameMode == MachinePlaysBlack) {
10513             StartClocks();
10514         } else {
10515             DisplayBothClocks();
10516         }
10517         if (gameMode == PlayFromGameFile) {
10518             if (appData.timeDelay >= 0) 
10519                 AutoPlayGameLoop();
10520         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10521             Reset(FALSE, TRUE);
10522             SendToICS(ics_prefix);
10523             SendToICS("refresh\n");
10524         } else if (currentMove < forwardMostMove) {
10525             ForwardInner(forwardMostMove);
10526         }
10527         pauseExamInvalid = FALSE;
10528     } else {
10529         switch (gameMode) {
10530           default:
10531             return;
10532           case IcsExamining:
10533             pauseExamForwardMostMove = forwardMostMove;
10534             pauseExamInvalid = FALSE;
10535             /* fall through */
10536           case IcsObserving:
10537           case IcsPlayingWhite:
10538           case IcsPlayingBlack:
10539             pausing = TRUE;
10540             ModeHighlight();
10541             return;
10542           case PlayFromGameFile:
10543             (void) StopLoadGameTimer();
10544             pausing = TRUE;
10545             ModeHighlight();
10546             break;
10547           case BeginningOfGame:
10548             if (appData.icsActive) return;
10549             /* else fall through */
10550           case MachinePlaysWhite:
10551           case MachinePlaysBlack:
10552           case TwoMachinesPlay:
10553             if (forwardMostMove == 0)
10554               return;           /* don't pause if no one has moved */
10555             if ((gameMode == MachinePlaysWhite &&
10556                  !WhiteOnMove(forwardMostMove)) ||
10557                 (gameMode == MachinePlaysBlack &&
10558                  WhiteOnMove(forwardMostMove))) {
10559                 StopClocks();
10560             }
10561             pausing = TRUE;
10562             ModeHighlight();
10563             break;
10564         }
10565     }
10566 }
10567
10568 void
10569 EditCommentEvent()
10570 {
10571     char title[MSG_SIZ];
10572
10573     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10574         strcpy(title, _("Edit comment"));
10575     } else {
10576         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10577                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10578                 parseList[currentMove - 1]);
10579     }
10580
10581     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10582 }
10583
10584
10585 void
10586 EditTagsEvent()
10587 {
10588     char *tags = PGNTags(&gameInfo);
10589     EditTagsPopUp(tags);
10590     free(tags);
10591 }
10592
10593 void
10594 AnalyzeModeEvent()
10595 {
10596     if (appData.noChessProgram || gameMode == AnalyzeMode)
10597       return;
10598
10599     if (gameMode != AnalyzeFile) {
10600         if (!appData.icsEngineAnalyze) {
10601                EditGameEvent();
10602                if (gameMode != EditGame) return;
10603         }
10604         ResurrectChessProgram();
10605         SendToProgram("analyze\n", &first);
10606         first.analyzing = TRUE;
10607         /*first.maybeThinking = TRUE;*/
10608         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10609         EngineOutputPopUp();
10610     }
10611     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10612     pausing = FALSE;
10613     ModeHighlight();
10614     SetGameInfo();
10615
10616     StartAnalysisClock();
10617     GetTimeMark(&lastNodeCountTime);
10618     lastNodeCount = 0;
10619 }
10620
10621 void
10622 AnalyzeFileEvent()
10623 {
10624     if (appData.noChessProgram || gameMode == AnalyzeFile)
10625       return;
10626
10627     if (gameMode != AnalyzeMode) {
10628         EditGameEvent();
10629         if (gameMode != EditGame) return;
10630         ResurrectChessProgram();
10631         SendToProgram("analyze\n", &first);
10632         first.analyzing = TRUE;
10633         /*first.maybeThinking = TRUE;*/
10634         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10635         EngineOutputPopUp();
10636     }
10637     gameMode = AnalyzeFile;
10638     pausing = FALSE;
10639     ModeHighlight();
10640     SetGameInfo();
10641
10642     StartAnalysisClock();
10643     GetTimeMark(&lastNodeCountTime);
10644     lastNodeCount = 0;
10645 }
10646
10647 void
10648 MachineWhiteEvent()
10649 {
10650     char buf[MSG_SIZ];
10651     char *bookHit = NULL;
10652
10653     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10654       return;
10655
10656
10657     if (gameMode == PlayFromGameFile || 
10658         gameMode == TwoMachinesPlay  || 
10659         gameMode == Training         || 
10660         gameMode == AnalyzeMode      || 
10661         gameMode == EndOfGame)
10662         EditGameEvent();
10663
10664     if (gameMode == EditPosition) 
10665         EditPositionDone(TRUE);
10666
10667     if (!WhiteOnMove(currentMove)) {
10668         DisplayError(_("It is not White's turn"), 0);
10669         return;
10670     }
10671   
10672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10673       ExitAnalyzeMode();
10674
10675     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10676         gameMode == AnalyzeFile)
10677         TruncateGame();
10678
10679     ResurrectChessProgram();    /* in case it isn't running */
10680     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10681         gameMode = MachinePlaysWhite;
10682         ResetClocks();
10683     } else
10684     gameMode = MachinePlaysWhite;
10685     pausing = FALSE;
10686     ModeHighlight();
10687     SetGameInfo();
10688     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10689     DisplayTitle(buf);
10690     if (first.sendName) {
10691       sprintf(buf, "name %s\n", gameInfo.black);
10692       SendToProgram(buf, &first);
10693     }
10694     if (first.sendTime) {
10695       if (first.useColors) {
10696         SendToProgram("black\n", &first); /*gnu kludge*/
10697       }
10698       SendTimeRemaining(&first, TRUE);
10699     }
10700     if (first.useColors) {
10701       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10702     }
10703     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10704     SetMachineThinkingEnables();
10705     first.maybeThinking = TRUE;
10706     StartClocks();
10707     firstMove = FALSE;
10708
10709     if (appData.autoFlipView && !flipView) {
10710       flipView = !flipView;
10711       DrawPosition(FALSE, NULL);
10712       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10713     }
10714
10715     if(bookHit) { // [HGM] book: simulate book reply
10716         static char bookMove[MSG_SIZ]; // a bit generous?
10717
10718         programStats.nodes = programStats.depth = programStats.time = 
10719         programStats.score = programStats.got_only_move = 0;
10720         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10721
10722         strcpy(bookMove, "move ");
10723         strcat(bookMove, bookHit);
10724         HandleMachineMove(bookMove, &first);
10725     }
10726 }
10727
10728 void
10729 MachineBlackEvent()
10730 {
10731     char buf[MSG_SIZ];
10732    char *bookHit = NULL;
10733
10734     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10735         return;
10736
10737
10738     if (gameMode == PlayFromGameFile || 
10739         gameMode == TwoMachinesPlay  || 
10740         gameMode == Training         || 
10741         gameMode == AnalyzeMode      || 
10742         gameMode == EndOfGame)
10743         EditGameEvent();
10744
10745     if (gameMode == EditPosition) 
10746         EditPositionDone(TRUE);
10747
10748     if (WhiteOnMove(currentMove)) {
10749         DisplayError(_("It is not Black's turn"), 0);
10750         return;
10751     }
10752     
10753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10754       ExitAnalyzeMode();
10755
10756     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10757         gameMode == AnalyzeFile)
10758         TruncateGame();
10759
10760     ResurrectChessProgram();    /* in case it isn't running */
10761     gameMode = MachinePlaysBlack;
10762     pausing = FALSE;
10763     ModeHighlight();
10764     SetGameInfo();
10765     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10766     DisplayTitle(buf);
10767     if (first.sendName) {
10768       sprintf(buf, "name %s\n", gameInfo.white);
10769       SendToProgram(buf, &first);
10770     }
10771     if (first.sendTime) {
10772       if (first.useColors) {
10773         SendToProgram("white\n", &first); /*gnu kludge*/
10774       }
10775       SendTimeRemaining(&first, FALSE);
10776     }
10777     if (first.useColors) {
10778       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10779     }
10780     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10781     SetMachineThinkingEnables();
10782     first.maybeThinking = TRUE;
10783     StartClocks();
10784
10785     if (appData.autoFlipView && flipView) {
10786       flipView = !flipView;
10787       DrawPosition(FALSE, NULL);
10788       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10789     }
10790     if(bookHit) { // [HGM] book: simulate book reply
10791         static char bookMove[MSG_SIZ]; // a bit generous?
10792
10793         programStats.nodes = programStats.depth = programStats.time = 
10794         programStats.score = programStats.got_only_move = 0;
10795         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10796
10797         strcpy(bookMove, "move ");
10798         strcat(bookMove, bookHit);
10799         HandleMachineMove(bookMove, &first);
10800     }
10801 }
10802
10803
10804 void
10805 DisplayTwoMachinesTitle()
10806 {
10807     char buf[MSG_SIZ];
10808     if (appData.matchGames > 0) {
10809         if (first.twoMachinesColor[0] == 'w') {
10810             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10811                     gameInfo.white, gameInfo.black,
10812                     first.matchWins, second.matchWins,
10813                     matchGame - 1 - (first.matchWins + second.matchWins));
10814         } else {
10815             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10816                     gameInfo.white, gameInfo.black,
10817                     second.matchWins, first.matchWins,
10818                     matchGame - 1 - (first.matchWins + second.matchWins));
10819         }
10820     } else {
10821         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10822     }
10823     DisplayTitle(buf);
10824 }
10825
10826 void
10827 TwoMachinesEvent P((void))
10828 {
10829     int i;
10830     char buf[MSG_SIZ];
10831     ChessProgramState *onmove;
10832     char *bookHit = NULL;
10833     
10834     if (appData.noChessProgram) return;
10835
10836     switch (gameMode) {
10837       case TwoMachinesPlay:
10838         return;
10839       case MachinePlaysWhite:
10840       case MachinePlaysBlack:
10841         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10842             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10843             return;
10844         }
10845         /* fall through */
10846       case BeginningOfGame:
10847       case PlayFromGameFile:
10848       case EndOfGame:
10849         EditGameEvent();
10850         if (gameMode != EditGame) return;
10851         break;
10852       case EditPosition:
10853         EditPositionDone(TRUE);
10854         break;
10855       case AnalyzeMode:
10856       case AnalyzeFile:
10857         ExitAnalyzeMode();
10858         break;
10859       case EditGame:
10860       default:
10861         break;
10862     }
10863
10864     forwardMostMove = currentMove;
10865     ResurrectChessProgram();    /* in case first program isn't running */
10866
10867     if (second.pr == NULL) {
10868         StartChessProgram(&second);
10869         if (second.protocolVersion == 1) {
10870           TwoMachinesEventIfReady();
10871         } else {
10872           /* kludge: allow timeout for initial "feature" command */
10873           FreezeUI();
10874           DisplayMessage("", _("Starting second chess program"));
10875           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10876         }
10877         return;
10878     }
10879     DisplayMessage("", "");
10880     InitChessProgram(&second, FALSE);
10881     SendToProgram("force\n", &second);
10882     if (startedFromSetupPosition) {
10883         SendBoard(&second, backwardMostMove);
10884     if (appData.debugMode) {
10885         fprintf(debugFP, "Two Machines\n");
10886     }
10887     }
10888     for (i = backwardMostMove; i < forwardMostMove; i++) {
10889         SendMoveToProgram(i, &second);
10890     }
10891
10892     gameMode = TwoMachinesPlay;
10893     pausing = FALSE;
10894     ModeHighlight();
10895     SetGameInfo();
10896     DisplayTwoMachinesTitle();
10897     firstMove = TRUE;
10898     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10899         onmove = &first;
10900     } else {
10901         onmove = &second;
10902     }
10903
10904     SendToProgram(first.computerString, &first);
10905     if (first.sendName) {
10906       sprintf(buf, "name %s\n", second.tidy);
10907       SendToProgram(buf, &first);
10908     }
10909     SendToProgram(second.computerString, &second);
10910     if (second.sendName) {
10911       sprintf(buf, "name %s\n", first.tidy);
10912       SendToProgram(buf, &second);
10913     }
10914
10915     ResetClocks();
10916     if (!first.sendTime || !second.sendTime) {
10917         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10918         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10919     }
10920     if (onmove->sendTime) {
10921       if (onmove->useColors) {
10922         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10923       }
10924       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10925     }
10926     if (onmove->useColors) {
10927       SendToProgram(onmove->twoMachinesColor, onmove);
10928     }
10929     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10930 //    SendToProgram("go\n", onmove);
10931     onmove->maybeThinking = TRUE;
10932     SetMachineThinkingEnables();
10933
10934     StartClocks();
10935
10936     if(bookHit) { // [HGM] book: simulate book reply
10937         static char bookMove[MSG_SIZ]; // a bit generous?
10938
10939         programStats.nodes = programStats.depth = programStats.time = 
10940         programStats.score = programStats.got_only_move = 0;
10941         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10942
10943         strcpy(bookMove, "move ");
10944         strcat(bookMove, bookHit);
10945         savedMessage = bookMove; // args for deferred call
10946         savedState = onmove;
10947         ScheduleDelayedEvent(DeferredBookMove, 1);
10948     }
10949 }
10950
10951 void
10952 TrainingEvent()
10953 {
10954     if (gameMode == Training) {
10955       SetTrainingModeOff();
10956       gameMode = PlayFromGameFile;
10957       DisplayMessage("", _("Training mode off"));
10958     } else {
10959       gameMode = Training;
10960       animateTraining = appData.animate;
10961
10962       /* make sure we are not already at the end of the game */
10963       if (currentMove < forwardMostMove) {
10964         SetTrainingModeOn();
10965         DisplayMessage("", _("Training mode on"));
10966       } else {
10967         gameMode = PlayFromGameFile;
10968         DisplayError(_("Already at end of game"), 0);
10969       }
10970     }
10971     ModeHighlight();
10972 }
10973
10974 void
10975 IcsClientEvent()
10976 {
10977     if (!appData.icsActive) return;
10978     switch (gameMode) {
10979       case IcsPlayingWhite:
10980       case IcsPlayingBlack:
10981       case IcsObserving:
10982       case IcsIdle:
10983       case BeginningOfGame:
10984       case IcsExamining:
10985         return;
10986
10987       case EditGame:
10988         break;
10989
10990       case EditPosition:
10991         EditPositionDone(TRUE);
10992         break;
10993
10994       case AnalyzeMode:
10995       case AnalyzeFile:
10996         ExitAnalyzeMode();
10997         break;
10998         
10999       default:
11000         EditGameEvent();
11001         break;
11002     }
11003
11004     gameMode = IcsIdle;
11005     ModeHighlight();
11006     return;
11007 }
11008
11009
11010 void
11011 EditGameEvent()
11012 {
11013     int i;
11014
11015     switch (gameMode) {
11016       case Training:
11017         SetTrainingModeOff();
11018         break;
11019       case MachinePlaysWhite:
11020       case MachinePlaysBlack:
11021       case BeginningOfGame:
11022         SendToProgram("force\n", &first);
11023         SetUserThinkingEnables();
11024         break;
11025       case PlayFromGameFile:
11026         (void) StopLoadGameTimer();
11027         if (gameFileFP != NULL) {
11028             gameFileFP = NULL;
11029         }
11030         break;
11031       case EditPosition:
11032         EditPositionDone(TRUE);
11033         break;
11034       case AnalyzeMode:
11035       case AnalyzeFile:
11036         ExitAnalyzeMode();
11037         SendToProgram("force\n", &first);
11038         break;
11039       case TwoMachinesPlay:
11040         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11041         ResurrectChessProgram();
11042         SetUserThinkingEnables();
11043         break;
11044       case EndOfGame:
11045         ResurrectChessProgram();
11046         break;
11047       case IcsPlayingBlack:
11048       case IcsPlayingWhite:
11049         DisplayError(_("Warning: You are still playing a game"), 0);
11050         break;
11051       case IcsObserving:
11052         DisplayError(_("Warning: You are still observing a game"), 0);
11053         break;
11054       case IcsExamining:
11055         DisplayError(_("Warning: You are still examining a game"), 0);
11056         break;
11057       case IcsIdle:
11058         break;
11059       case EditGame:
11060       default:
11061         return;
11062     }
11063     
11064     pausing = FALSE;
11065     StopClocks();
11066     first.offeredDraw = second.offeredDraw = 0;
11067
11068     if (gameMode == PlayFromGameFile) {
11069         whiteTimeRemaining = timeRemaining[0][currentMove];
11070         blackTimeRemaining = timeRemaining[1][currentMove];
11071         DisplayTitle("");
11072     }
11073
11074     if (gameMode == MachinePlaysWhite ||
11075         gameMode == MachinePlaysBlack ||
11076         gameMode == TwoMachinesPlay ||
11077         gameMode == EndOfGame) {
11078         i = forwardMostMove;
11079         while (i > currentMove) {
11080             SendToProgram("undo\n", &first);
11081             i--;
11082         }
11083         whiteTimeRemaining = timeRemaining[0][currentMove];
11084         blackTimeRemaining = timeRemaining[1][currentMove];
11085         DisplayBothClocks();
11086         if (whiteFlag || blackFlag) {
11087             whiteFlag = blackFlag = 0;
11088         }
11089         DisplayTitle("");
11090     }           
11091     
11092     gameMode = EditGame;
11093     ModeHighlight();
11094     SetGameInfo();
11095 }
11096
11097
11098 void
11099 EditPositionEvent()
11100 {
11101     if (gameMode == EditPosition) {
11102         EditGameEvent();
11103         return;
11104     }
11105     
11106     EditGameEvent();
11107     if (gameMode != EditGame) return;
11108     
11109     gameMode = EditPosition;
11110     ModeHighlight();
11111     SetGameInfo();
11112     if (currentMove > 0)
11113       CopyBoard(boards[0], boards[currentMove]);
11114     
11115     blackPlaysFirst = !WhiteOnMove(currentMove);
11116     ResetClocks();
11117     currentMove = forwardMostMove = backwardMostMove = 0;
11118     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11119     DisplayMove(-1);
11120 }
11121
11122 void
11123 ExitAnalyzeMode()
11124 {
11125     /* [DM] icsEngineAnalyze - possible call from other functions */
11126     if (appData.icsEngineAnalyze) {
11127         appData.icsEngineAnalyze = FALSE;
11128
11129         DisplayMessage("",_("Close ICS engine analyze..."));
11130     }
11131     if (first.analysisSupport && first.analyzing) {
11132       SendToProgram("exit\n", &first);
11133       first.analyzing = FALSE;
11134     }
11135     thinkOutput[0] = NULLCHAR;
11136 }
11137
11138 void
11139 EditPositionDone(Boolean fakeRights)
11140 {
11141     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11142
11143     startedFromSetupPosition = TRUE;
11144     InitChessProgram(&first, FALSE);
11145     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11146       boards[0][EP_STATUS] = EP_NONE;
11147       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11148     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11149         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11150         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11151       } else boards[0][CASTLING][2] = NoRights;
11152     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11153         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11154         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11155       } else boards[0][CASTLING][5] = NoRights;
11156     }
11157     SendToProgram("force\n", &first);
11158     if (blackPlaysFirst) {
11159         strcpy(moveList[0], "");
11160         strcpy(parseList[0], "");
11161         currentMove = forwardMostMove = backwardMostMove = 1;
11162         CopyBoard(boards[1], boards[0]);
11163     } else {
11164         currentMove = forwardMostMove = backwardMostMove = 0;
11165     }
11166     SendBoard(&first, forwardMostMove);
11167     if (appData.debugMode) {
11168         fprintf(debugFP, "EditPosDone\n");
11169     }
11170     DisplayTitle("");
11171     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11172     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11173     gameMode = EditGame;
11174     ModeHighlight();
11175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11176     ClearHighlights(); /* [AS] */
11177 }
11178
11179 /* Pause for `ms' milliseconds */
11180 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11181 void
11182 TimeDelay(ms)
11183      long ms;
11184 {
11185     TimeMark m1, m2;
11186
11187     GetTimeMark(&m1);
11188     do {
11189         GetTimeMark(&m2);
11190     } while (SubtractTimeMarks(&m2, &m1) < ms);
11191 }
11192
11193 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11194 void
11195 SendMultiLineToICS(buf)
11196      char *buf;
11197 {
11198     char temp[MSG_SIZ+1], *p;
11199     int len;
11200
11201     len = strlen(buf);
11202     if (len > MSG_SIZ)
11203       len = MSG_SIZ;
11204   
11205     strncpy(temp, buf, len);
11206     temp[len] = 0;
11207
11208     p = temp;
11209     while (*p) {
11210         if (*p == '\n' || *p == '\r')
11211           *p = ' ';
11212         ++p;
11213     }
11214
11215     strcat(temp, "\n");
11216     SendToICS(temp);
11217     SendToPlayer(temp, strlen(temp));
11218 }
11219
11220 void
11221 SetWhiteToPlayEvent()
11222 {
11223     if (gameMode == EditPosition) {
11224         blackPlaysFirst = FALSE;
11225         DisplayBothClocks();    /* works because currentMove is 0 */
11226     } else if (gameMode == IcsExamining) {
11227         SendToICS(ics_prefix);
11228         SendToICS("tomove white\n");
11229     }
11230 }
11231
11232 void
11233 SetBlackToPlayEvent()
11234 {
11235     if (gameMode == EditPosition) {
11236         blackPlaysFirst = TRUE;
11237         currentMove = 1;        /* kludge */
11238         DisplayBothClocks();
11239         currentMove = 0;
11240     } else if (gameMode == IcsExamining) {
11241         SendToICS(ics_prefix);
11242         SendToICS("tomove black\n");
11243     }
11244 }
11245
11246 void
11247 EditPositionMenuEvent(selection, x, y)
11248      ChessSquare selection;
11249      int x, y;
11250 {
11251     char buf[MSG_SIZ];
11252     ChessSquare piece = boards[0][y][x];
11253
11254     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11255
11256     switch (selection) {
11257       case ClearBoard:
11258         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11259             SendToICS(ics_prefix);
11260             SendToICS("bsetup clear\n");
11261         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11262             SendToICS(ics_prefix);
11263             SendToICS("clearboard\n");
11264         } else {
11265             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11266                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11267                 for (y = 0; y < BOARD_HEIGHT; y++) {
11268                     if (gameMode == IcsExamining) {
11269                         if (boards[currentMove][y][x] != EmptySquare) {
11270                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11271                                     AAA + x, ONE + y);
11272                             SendToICS(buf);
11273                         }
11274                     } else {
11275                         boards[0][y][x] = p;
11276                     }
11277                 }
11278             }
11279         }
11280         if (gameMode == EditPosition) {
11281             DrawPosition(FALSE, boards[0]);
11282         }
11283         break;
11284
11285       case WhitePlay:
11286         SetWhiteToPlayEvent();
11287         break;
11288
11289       case BlackPlay:
11290         SetBlackToPlayEvent();
11291         break;
11292
11293       case EmptySquare:
11294         if (gameMode == IcsExamining) {
11295             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11296             SendToICS(buf);
11297         } else {
11298             boards[0][y][x] = EmptySquare;
11299             DrawPosition(FALSE, boards[0]);
11300         }
11301         break;
11302
11303       case PromotePiece:
11304         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11305            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11306             selection = (ChessSquare) (PROMOTED piece);
11307         } else if(piece == EmptySquare) selection = WhiteSilver;
11308         else selection = (ChessSquare)((int)piece - 1);
11309         goto defaultlabel;
11310
11311       case DemotePiece:
11312         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11313            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11314             selection = (ChessSquare) (DEMOTED piece);
11315         } else if(piece == EmptySquare) selection = BlackSilver;
11316         else selection = (ChessSquare)((int)piece + 1);       
11317         goto defaultlabel;
11318
11319       case WhiteQueen:
11320       case BlackQueen:
11321         if(gameInfo.variant == VariantShatranj ||
11322            gameInfo.variant == VariantXiangqi  ||
11323            gameInfo.variant == VariantCourier    )
11324             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11325         goto defaultlabel;
11326
11327       case WhiteKing:
11328       case BlackKing:
11329         if(gameInfo.variant == VariantXiangqi)
11330             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11331         if(gameInfo.variant == VariantKnightmate)
11332             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11333       default:
11334         defaultlabel:
11335         if (gameMode == IcsExamining) {
11336             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11337                     PieceToChar(selection), AAA + x, ONE + y);
11338             SendToICS(buf);
11339         } else {
11340             boards[0][y][x] = selection;
11341             DrawPosition(FALSE, boards[0]);
11342         }
11343         break;
11344     }
11345 }
11346
11347
11348 void
11349 DropMenuEvent(selection, x, y)
11350      ChessSquare selection;
11351      int x, y;
11352 {
11353     ChessMove moveType;
11354
11355     switch (gameMode) {
11356       case IcsPlayingWhite:
11357       case MachinePlaysBlack:
11358         if (!WhiteOnMove(currentMove)) {
11359             DisplayMoveError(_("It is Black's turn"));
11360             return;
11361         }
11362         moveType = WhiteDrop;
11363         break;
11364       case IcsPlayingBlack:
11365       case MachinePlaysWhite:
11366         if (WhiteOnMove(currentMove)) {
11367             DisplayMoveError(_("It is White's turn"));
11368             return;
11369         }
11370         moveType = BlackDrop;
11371         break;
11372       case EditGame:
11373         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11374         break;
11375       default:
11376         return;
11377     }
11378
11379     if (moveType == BlackDrop && selection < BlackPawn) {
11380       selection = (ChessSquare) ((int) selection
11381                                  + (int) BlackPawn - (int) WhitePawn);
11382     }
11383     if (boards[currentMove][y][x] != EmptySquare) {
11384         DisplayMoveError(_("That square is occupied"));
11385         return;
11386     }
11387
11388     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11389 }
11390
11391 void
11392 AcceptEvent()
11393 {
11394     /* Accept a pending offer of any kind from opponent */
11395     
11396     if (appData.icsActive) {
11397         SendToICS(ics_prefix);
11398         SendToICS("accept\n");
11399     } else if (cmailMsgLoaded) {
11400         if (currentMove == cmailOldMove &&
11401             commentList[cmailOldMove] != NULL &&
11402             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11403                    "Black offers a draw" : "White offers a draw")) {
11404             TruncateGame();
11405             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11406             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11407         } else {
11408             DisplayError(_("There is no pending offer on this move"), 0);
11409             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11410         }
11411     } else {
11412         /* Not used for offers from chess program */
11413     }
11414 }
11415
11416 void
11417 DeclineEvent()
11418 {
11419     /* Decline a pending offer of any kind from opponent */
11420     
11421     if (appData.icsActive) {
11422         SendToICS(ics_prefix);
11423         SendToICS("decline\n");
11424     } else if (cmailMsgLoaded) {
11425         if (currentMove == cmailOldMove &&
11426             commentList[cmailOldMove] != NULL &&
11427             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11428                    "Black offers a draw" : "White offers a draw")) {
11429 #ifdef NOTDEF
11430             AppendComment(cmailOldMove, "Draw declined", TRUE);
11431             DisplayComment(cmailOldMove - 1, "Draw declined");
11432 #endif /*NOTDEF*/
11433         } else {
11434             DisplayError(_("There is no pending offer on this move"), 0);
11435         }
11436     } else {
11437         /* Not used for offers from chess program */
11438     }
11439 }
11440
11441 void
11442 RematchEvent()
11443 {
11444     /* Issue ICS rematch command */
11445     if (appData.icsActive) {
11446         SendToICS(ics_prefix);
11447         SendToICS("rematch\n");
11448     }
11449 }
11450
11451 void
11452 CallFlagEvent()
11453 {
11454     /* Call your opponent's flag (claim a win on time) */
11455     if (appData.icsActive) {
11456         SendToICS(ics_prefix);
11457         SendToICS("flag\n");
11458     } else {
11459         switch (gameMode) {
11460           default:
11461             return;
11462           case MachinePlaysWhite:
11463             if (whiteFlag) {
11464                 if (blackFlag)
11465                   GameEnds(GameIsDrawn, "Both players ran out of time",
11466                            GE_PLAYER);
11467                 else
11468                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11469             } else {
11470                 DisplayError(_("Your opponent is not out of time"), 0);
11471             }
11472             break;
11473           case MachinePlaysBlack:
11474             if (blackFlag) {
11475                 if (whiteFlag)
11476                   GameEnds(GameIsDrawn, "Both players ran out of time",
11477                            GE_PLAYER);
11478                 else
11479                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11480             } else {
11481                 DisplayError(_("Your opponent is not out of time"), 0);
11482             }
11483             break;
11484         }
11485     }
11486 }
11487
11488 void
11489 DrawEvent()
11490 {
11491     /* Offer draw or accept pending draw offer from opponent */
11492     
11493     if (appData.icsActive) {
11494         /* Note: tournament rules require draw offers to be
11495            made after you make your move but before you punch
11496            your clock.  Currently ICS doesn't let you do that;
11497            instead, you immediately punch your clock after making
11498            a move, but you can offer a draw at any time. */
11499         
11500         SendToICS(ics_prefix);
11501         SendToICS("draw\n");
11502     } else if (cmailMsgLoaded) {
11503         if (currentMove == cmailOldMove &&
11504             commentList[cmailOldMove] != NULL &&
11505             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11506                    "Black offers a draw" : "White offers a draw")) {
11507             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11508             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11509         } else if (currentMove == cmailOldMove + 1) {
11510             char *offer = WhiteOnMove(cmailOldMove) ?
11511               "White offers a draw" : "Black offers a draw";
11512             AppendComment(currentMove, offer, TRUE);
11513             DisplayComment(currentMove - 1, offer);
11514             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11515         } else {
11516             DisplayError(_("You must make your move before offering a draw"), 0);
11517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11518         }
11519     } else if (first.offeredDraw) {
11520         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11521     } else {
11522         if (first.sendDrawOffers) {
11523             SendToProgram("draw\n", &first);
11524             userOfferedDraw = TRUE;
11525         }
11526     }
11527 }
11528
11529 void
11530 AdjournEvent()
11531 {
11532     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11533     
11534     if (appData.icsActive) {
11535         SendToICS(ics_prefix);
11536         SendToICS("adjourn\n");
11537     } else {
11538         /* Currently GNU Chess doesn't offer or accept Adjourns */
11539     }
11540 }
11541
11542
11543 void
11544 AbortEvent()
11545 {
11546     /* Offer Abort or accept pending Abort offer from opponent */
11547     
11548     if (appData.icsActive) {
11549         SendToICS(ics_prefix);
11550         SendToICS("abort\n");
11551     } else {
11552         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11553     }
11554 }
11555
11556 void
11557 ResignEvent()
11558 {
11559     /* Resign.  You can do this even if it's not your turn. */
11560     
11561     if (appData.icsActive) {
11562         SendToICS(ics_prefix);
11563         SendToICS("resign\n");
11564     } else {
11565         switch (gameMode) {
11566           case MachinePlaysWhite:
11567             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11568             break;
11569           case MachinePlaysBlack:
11570             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11571             break;
11572           case EditGame:
11573             if (cmailMsgLoaded) {
11574                 TruncateGame();
11575                 if (WhiteOnMove(cmailOldMove)) {
11576                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11577                 } else {
11578                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11579                 }
11580                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11581             }
11582             break;
11583           default:
11584             break;
11585         }
11586     }
11587 }
11588
11589
11590 void
11591 StopObservingEvent()
11592 {
11593     /* Stop observing current games */
11594     SendToICS(ics_prefix);
11595     SendToICS("unobserve\n");
11596 }
11597
11598 void
11599 StopExaminingEvent()
11600 {
11601     /* Stop observing current game */
11602     SendToICS(ics_prefix);
11603     SendToICS("unexamine\n");
11604 }
11605
11606 void
11607 ForwardInner(target)
11608      int target;
11609 {
11610     int limit;
11611
11612     if (appData.debugMode)
11613         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11614                 target, currentMove, forwardMostMove);
11615
11616     if (gameMode == EditPosition)
11617       return;
11618
11619     if (gameMode == PlayFromGameFile && !pausing)
11620       PauseEvent();
11621     
11622     if (gameMode == IcsExamining && pausing)
11623       limit = pauseExamForwardMostMove;
11624     else
11625       limit = forwardMostMove;
11626     
11627     if (target > limit) target = limit;
11628
11629     if (target > 0 && moveList[target - 1][0]) {
11630         int fromX, fromY, toX, toY;
11631         toX = moveList[target - 1][2] - AAA;
11632         toY = moveList[target - 1][3] - ONE;
11633         if (moveList[target - 1][1] == '@') {
11634             if (appData.highlightLastMove) {
11635                 SetHighlights(-1, -1, toX, toY);
11636             }
11637         } else {
11638             fromX = moveList[target - 1][0] - AAA;
11639             fromY = moveList[target - 1][1] - ONE;
11640             if (target == currentMove + 1) {
11641                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11642             }
11643             if (appData.highlightLastMove) {
11644                 SetHighlights(fromX, fromY, toX, toY);
11645             }
11646         }
11647     }
11648     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11649         gameMode == Training || gameMode == PlayFromGameFile || 
11650         gameMode == AnalyzeFile) {
11651         while (currentMove < target) {
11652             SendMoveToProgram(currentMove++, &first);
11653         }
11654     } else {
11655         currentMove = target;
11656     }
11657     
11658     if (gameMode == EditGame || gameMode == EndOfGame) {
11659         whiteTimeRemaining = timeRemaining[0][currentMove];
11660         blackTimeRemaining = timeRemaining[1][currentMove];
11661     }
11662     DisplayBothClocks();
11663     DisplayMove(currentMove - 1);
11664     DrawPosition(FALSE, boards[currentMove]);
11665     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11666     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11667         DisplayComment(currentMove - 1, commentList[currentMove]);
11668     }
11669 }
11670
11671
11672 void
11673 ForwardEvent()
11674 {
11675     if (gameMode == IcsExamining && !pausing) {
11676         SendToICS(ics_prefix);
11677         SendToICS("forward\n");
11678     } else {
11679         ForwardInner(currentMove + 1);
11680     }
11681 }
11682
11683 void
11684 ToEndEvent()
11685 {
11686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11687         /* to optimze, we temporarily turn off analysis mode while we feed
11688          * the remaining moves to the engine. Otherwise we get analysis output
11689          * after each move.
11690          */ 
11691         if (first.analysisSupport) {
11692           SendToProgram("exit\nforce\n", &first);
11693           first.analyzing = FALSE;
11694         }
11695     }
11696         
11697     if (gameMode == IcsExamining && !pausing) {
11698         SendToICS(ics_prefix);
11699         SendToICS("forward 999999\n");
11700     } else {
11701         ForwardInner(forwardMostMove);
11702     }
11703
11704     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11705         /* we have fed all the moves, so reactivate analysis mode */
11706         SendToProgram("analyze\n", &first);
11707         first.analyzing = TRUE;
11708         /*first.maybeThinking = TRUE;*/
11709         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11710     }
11711 }
11712
11713 void
11714 BackwardInner(target)
11715      int target;
11716 {
11717     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11718
11719     if (appData.debugMode)
11720         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11721                 target, currentMove, forwardMostMove);
11722
11723     if (gameMode == EditPosition) return;
11724     if (currentMove <= backwardMostMove) {
11725         ClearHighlights();
11726         DrawPosition(full_redraw, boards[currentMove]);
11727         return;
11728     }
11729     if (gameMode == PlayFromGameFile && !pausing)
11730       PauseEvent();
11731     
11732     if (moveList[target][0]) {
11733         int fromX, fromY, toX, toY;
11734         toX = moveList[target][2] - AAA;
11735         toY = moveList[target][3] - ONE;
11736         if (moveList[target][1] == '@') {
11737             if (appData.highlightLastMove) {
11738                 SetHighlights(-1, -1, toX, toY);
11739             }
11740         } else {
11741             fromX = moveList[target][0] - AAA;
11742             fromY = moveList[target][1] - ONE;
11743             if (target == currentMove - 1) {
11744                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11745             }
11746             if (appData.highlightLastMove) {
11747                 SetHighlights(fromX, fromY, toX, toY);
11748             }
11749         }
11750     }
11751     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11752         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11753         while (currentMove > target) {
11754             SendToProgram("undo\n", &first);
11755             currentMove--;
11756         }
11757     } else {
11758         currentMove = target;
11759     }
11760     
11761     if (gameMode == EditGame || gameMode == EndOfGame) {
11762         whiteTimeRemaining = timeRemaining[0][currentMove];
11763         blackTimeRemaining = timeRemaining[1][currentMove];
11764     }
11765     DisplayBothClocks();
11766     DisplayMove(currentMove - 1);
11767     DrawPosition(full_redraw, boards[currentMove]);
11768     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11769     // [HGM] PV info: routine tests if comment empty
11770     DisplayComment(currentMove - 1, commentList[currentMove]);
11771 }
11772
11773 void
11774 BackwardEvent()
11775 {
11776     if (gameMode == IcsExamining && !pausing) {
11777         SendToICS(ics_prefix);
11778         SendToICS("backward\n");
11779     } else {
11780         BackwardInner(currentMove - 1);
11781     }
11782 }
11783
11784 void
11785 ToStartEvent()
11786 {
11787     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11788         /* to optimze, we temporarily turn off analysis mode while we undo
11789          * all the moves. Otherwise we get analysis output after each undo.
11790          */ 
11791         if (first.analysisSupport) {
11792           SendToProgram("exit\nforce\n", &first);
11793           first.analyzing = FALSE;
11794         }
11795     }
11796
11797     if (gameMode == IcsExamining && !pausing) {
11798         SendToICS(ics_prefix);
11799         SendToICS("backward 999999\n");
11800     } else {
11801         BackwardInner(backwardMostMove);
11802     }
11803
11804     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11805         /* we have fed all the moves, so reactivate analysis mode */
11806         SendToProgram("analyze\n", &first);
11807         first.analyzing = TRUE;
11808         /*first.maybeThinking = TRUE;*/
11809         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11810     }
11811 }
11812
11813 void
11814 ToNrEvent(int to)
11815 {
11816   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11817   if (to >= forwardMostMove) to = forwardMostMove;
11818   if (to <= backwardMostMove) to = backwardMostMove;
11819   if (to < currentMove) {
11820     BackwardInner(to);
11821   } else {
11822     ForwardInner(to);
11823   }
11824 }
11825
11826 void
11827 RevertEvent()
11828 {
11829     if (gameMode != IcsExamining) {
11830         DisplayError(_("You are not examining a game"), 0);
11831         return;
11832     }
11833     if (pausing) {
11834         DisplayError(_("You can't revert while pausing"), 0);
11835         return;
11836     }
11837     SendToICS(ics_prefix);
11838     SendToICS("revert\n");
11839 }
11840
11841 void
11842 RetractMoveEvent()
11843 {
11844     switch (gameMode) {
11845       case MachinePlaysWhite:
11846       case MachinePlaysBlack:
11847         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11848             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11849             return;
11850         }
11851         if (forwardMostMove < 2) return;
11852         currentMove = forwardMostMove = forwardMostMove - 2;
11853         whiteTimeRemaining = timeRemaining[0][currentMove];
11854         blackTimeRemaining = timeRemaining[1][currentMove];
11855         DisplayBothClocks();
11856         DisplayMove(currentMove - 1);
11857         ClearHighlights();/*!! could figure this out*/
11858         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11859         SendToProgram("remove\n", &first);
11860         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11861         break;
11862
11863       case BeginningOfGame:
11864       default:
11865         break;
11866
11867       case IcsPlayingWhite:
11868       case IcsPlayingBlack:
11869         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11870             SendToICS(ics_prefix);
11871             SendToICS("takeback 2\n");
11872         } else {
11873             SendToICS(ics_prefix);
11874             SendToICS("takeback 1\n");
11875         }
11876         break;
11877     }
11878 }
11879
11880 void
11881 MoveNowEvent()
11882 {
11883     ChessProgramState *cps;
11884
11885     switch (gameMode) {
11886       case MachinePlaysWhite:
11887         if (!WhiteOnMove(forwardMostMove)) {
11888             DisplayError(_("It is your turn"), 0);
11889             return;
11890         }
11891         cps = &first;
11892         break;
11893       case MachinePlaysBlack:
11894         if (WhiteOnMove(forwardMostMove)) {
11895             DisplayError(_("It is your turn"), 0);
11896             return;
11897         }
11898         cps = &first;
11899         break;
11900       case TwoMachinesPlay:
11901         if (WhiteOnMove(forwardMostMove) ==
11902             (first.twoMachinesColor[0] == 'w')) {
11903             cps = &first;
11904         } else {
11905             cps = &second;
11906         }
11907         break;
11908       case BeginningOfGame:
11909       default:
11910         return;
11911     }
11912     SendToProgram("?\n", cps);
11913 }
11914
11915 void
11916 TruncateGameEvent()
11917 {
11918     EditGameEvent();
11919     if (gameMode != EditGame) return;
11920     TruncateGame();
11921 }
11922
11923 void
11924 TruncateGame()
11925 {
11926     if (forwardMostMove > currentMove) {
11927         if (gameInfo.resultDetails != NULL) {
11928             free(gameInfo.resultDetails);
11929             gameInfo.resultDetails = NULL;
11930             gameInfo.result = GameUnfinished;
11931         }
11932         forwardMostMove = currentMove;
11933         HistorySet(parseList, backwardMostMove, forwardMostMove,
11934                    currentMove-1);
11935     }
11936 }
11937
11938 void
11939 HintEvent()
11940 {
11941     if (appData.noChessProgram) return;
11942     switch (gameMode) {
11943       case MachinePlaysWhite:
11944         if (WhiteOnMove(forwardMostMove)) {
11945             DisplayError(_("Wait until your turn"), 0);
11946             return;
11947         }
11948         break;
11949       case BeginningOfGame:
11950       case MachinePlaysBlack:
11951         if (!WhiteOnMove(forwardMostMove)) {
11952             DisplayError(_("Wait until your turn"), 0);
11953             return;
11954         }
11955         break;
11956       default:
11957         DisplayError(_("No hint available"), 0);
11958         return;
11959     }
11960     SendToProgram("hint\n", &first);
11961     hintRequested = TRUE;
11962 }
11963
11964 void
11965 BookEvent()
11966 {
11967     if (appData.noChessProgram) return;
11968     switch (gameMode) {
11969       case MachinePlaysWhite:
11970         if (WhiteOnMove(forwardMostMove)) {
11971             DisplayError(_("Wait until your turn"), 0);
11972             return;
11973         }
11974         break;
11975       case BeginningOfGame:
11976       case MachinePlaysBlack:
11977         if (!WhiteOnMove(forwardMostMove)) {
11978             DisplayError(_("Wait until your turn"), 0);
11979             return;
11980         }
11981         break;
11982       case EditPosition:
11983         EditPositionDone(TRUE);
11984         break;
11985       case TwoMachinesPlay:
11986         return;
11987       default:
11988         break;
11989     }
11990     SendToProgram("bk\n", &first);
11991     bookOutput[0] = NULLCHAR;
11992     bookRequested = TRUE;
11993 }
11994
11995 void
11996 AboutGameEvent()
11997 {
11998     char *tags = PGNTags(&gameInfo);
11999     TagsPopUp(tags, CmailMsg());
12000     free(tags);
12001 }
12002
12003 /* end button procedures */
12004
12005 void
12006 PrintPosition(fp, move)
12007      FILE *fp;
12008      int move;
12009 {
12010     int i, j;
12011     
12012     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12013         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12014             char c = PieceToChar(boards[move][i][j]);
12015             fputc(c == 'x' ? '.' : c, fp);
12016             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12017         }
12018     }
12019     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12020       fprintf(fp, "white to play\n");
12021     else
12022       fprintf(fp, "black to play\n");
12023 }
12024
12025 void
12026 PrintOpponents(fp)
12027      FILE *fp;
12028 {
12029     if (gameInfo.white != NULL) {
12030         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12031     } else {
12032         fprintf(fp, "\n");
12033     }
12034 }
12035
12036 /* Find last component of program's own name, using some heuristics */
12037 void
12038 TidyProgramName(prog, host, buf)
12039      char *prog, *host, buf[MSG_SIZ];
12040 {
12041     char *p, *q;
12042     int local = (strcmp(host, "localhost") == 0);
12043     while (!local && (p = strchr(prog, ';')) != NULL) {
12044         p++;
12045         while (*p == ' ') p++;
12046         prog = p;
12047     }
12048     if (*prog == '"' || *prog == '\'') {
12049         q = strchr(prog + 1, *prog);
12050     } else {
12051         q = strchr(prog, ' ');
12052     }
12053     if (q == NULL) q = prog + strlen(prog);
12054     p = q;
12055     while (p >= prog && *p != '/' && *p != '\\') p--;
12056     p++;
12057     if(p == prog && *p == '"') p++;
12058     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12059     memcpy(buf, p, q - p);
12060     buf[q - p] = NULLCHAR;
12061     if (!local) {
12062         strcat(buf, "@");
12063         strcat(buf, host);
12064     }
12065 }
12066
12067 char *
12068 TimeControlTagValue()
12069 {
12070     char buf[MSG_SIZ];
12071     if (!appData.clockMode) {
12072         strcpy(buf, "-");
12073     } else if (movesPerSession > 0) {
12074         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12075     } else if (timeIncrement == 0) {
12076         sprintf(buf, "%ld", timeControl/1000);
12077     } else {
12078         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12079     }
12080     return StrSave(buf);
12081 }
12082
12083 void
12084 SetGameInfo()
12085 {
12086     /* This routine is used only for certain modes */
12087     VariantClass v = gameInfo.variant;
12088     ClearGameInfo(&gameInfo);
12089     gameInfo.variant = v;
12090
12091     switch (gameMode) {
12092       case MachinePlaysWhite:
12093         gameInfo.event = StrSave( appData.pgnEventHeader );
12094         gameInfo.site = StrSave(HostName());
12095         gameInfo.date = PGNDate();
12096         gameInfo.round = StrSave("-");
12097         gameInfo.white = StrSave(first.tidy);
12098         gameInfo.black = StrSave(UserName());
12099         gameInfo.timeControl = TimeControlTagValue();
12100         break;
12101
12102       case MachinePlaysBlack:
12103         gameInfo.event = StrSave( appData.pgnEventHeader );
12104         gameInfo.site = StrSave(HostName());
12105         gameInfo.date = PGNDate();
12106         gameInfo.round = StrSave("-");
12107         gameInfo.white = StrSave(UserName());
12108         gameInfo.black = StrSave(first.tidy);
12109         gameInfo.timeControl = TimeControlTagValue();
12110         break;
12111
12112       case TwoMachinesPlay:
12113         gameInfo.event = StrSave( appData.pgnEventHeader );
12114         gameInfo.site = StrSave(HostName());
12115         gameInfo.date = PGNDate();
12116         if (matchGame > 0) {
12117             char buf[MSG_SIZ];
12118             sprintf(buf, "%d", matchGame);
12119             gameInfo.round = StrSave(buf);
12120         } else {
12121             gameInfo.round = StrSave("-");
12122         }
12123         if (first.twoMachinesColor[0] == 'w') {
12124             gameInfo.white = StrSave(first.tidy);
12125             gameInfo.black = StrSave(second.tidy);
12126         } else {
12127             gameInfo.white = StrSave(second.tidy);
12128             gameInfo.black = StrSave(first.tidy);
12129         }
12130         gameInfo.timeControl = TimeControlTagValue();
12131         break;
12132
12133       case EditGame:
12134         gameInfo.event = StrSave("Edited game");
12135         gameInfo.site = StrSave(HostName());
12136         gameInfo.date = PGNDate();
12137         gameInfo.round = StrSave("-");
12138         gameInfo.white = StrSave("-");
12139         gameInfo.black = StrSave("-");
12140         break;
12141
12142       case EditPosition:
12143         gameInfo.event = StrSave("Edited position");
12144         gameInfo.site = StrSave(HostName());
12145         gameInfo.date = PGNDate();
12146         gameInfo.round = StrSave("-");
12147         gameInfo.white = StrSave("-");
12148         gameInfo.black = StrSave("-");
12149         break;
12150
12151       case IcsPlayingWhite:
12152       case IcsPlayingBlack:
12153       case IcsObserving:
12154       case IcsExamining:
12155         break;
12156
12157       case PlayFromGameFile:
12158         gameInfo.event = StrSave("Game from non-PGN file");
12159         gameInfo.site = StrSave(HostName());
12160         gameInfo.date = PGNDate();
12161         gameInfo.round = StrSave("-");
12162         gameInfo.white = StrSave("?");
12163         gameInfo.black = StrSave("?");
12164         break;
12165
12166       default:
12167         break;
12168     }
12169 }
12170
12171 void
12172 ReplaceComment(index, text)
12173      int index;
12174      char *text;
12175 {
12176     int len;
12177
12178     while (*text == '\n') text++;
12179     len = strlen(text);
12180     while (len > 0 && text[len - 1] == '\n') len--;
12181
12182     if (commentList[index] != NULL)
12183       free(commentList[index]);
12184
12185     if (len == 0) {
12186         commentList[index] = NULL;
12187         return;
12188     }
12189   if(*text == '{' || *text == '(' || *text == '[') {
12190     commentList[index] = (char *) malloc(len + 2);
12191     strncpy(commentList[index], text, len);
12192     commentList[index][len] = '\n';
12193     commentList[index][len + 1] = NULLCHAR;
12194   } else { 
12195     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12196     char *p;
12197     commentList[index] = (char *) malloc(len + 6);
12198     strcpy(commentList[index], "{\n");
12199     strcat(commentList[index], text);
12200     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12201     strcat(commentList[index], "\n}");
12202   }
12203 }
12204
12205 void
12206 CrushCRs(text)
12207      char *text;
12208 {
12209   char *p = text;
12210   char *q = text;
12211   char ch;
12212
12213   do {
12214     ch = *p++;
12215     if (ch == '\r') continue;
12216     *q++ = ch;
12217   } while (ch != '\0');
12218 }
12219
12220 void
12221 AppendComment(index, text, addBraces)
12222      int index;
12223      char *text;
12224      Boolean addBraces; // [HGM] braces: tells if we should add {}
12225 {
12226     int oldlen, len;
12227     char *old;
12228
12229 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12230     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12231
12232     CrushCRs(text);
12233     while (*text == '\n') text++;
12234     len = strlen(text);
12235     while (len > 0 && text[len - 1] == '\n') len--;
12236
12237     if (len == 0) return;
12238
12239     if (commentList[index] != NULL) {
12240         old = commentList[index];
12241         oldlen = strlen(old);
12242         commentList[index] = (char *) malloc(oldlen + len + 4); // might waste 2
12243         strcpy(commentList[index], old);
12244         free(old);
12245         // [HGM] braces: join "{A\n}" + "{B}" as "{A\nB\n}"
12246         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12247           if(addBraces) addBraces = FALSE; else { text++; len--; }
12248           while (*text == '\n') { text++; len--; }
12249           commentList[index][oldlen-1] = NULLCHAR;
12250           oldlen--;
12251       }
12252         strncpy(&commentList[index][oldlen], text, len);
12253         if(addBraces) strcpy(&commentList[index][oldlen + len], "\n}");
12254         else          strcpy(&commentList[index][oldlen + len], "\n");
12255     } else {
12256         commentList[index] = (char *) malloc(len + 4); // perhaps wastes 2...
12257         if(addBraces) commentList[index][0] = '{';
12258         strcpy(commentList[index] + addBraces, text);
12259         strcat(commentList[index], "\n");
12260         if(addBraces) strcat(commentList[index], "}");
12261     }
12262 }
12263
12264 static char * FindStr( char * text, char * sub_text )
12265 {
12266     char * result = strstr( text, sub_text );
12267
12268     if( result != NULL ) {
12269         result += strlen( sub_text );
12270     }
12271
12272     return result;
12273 }
12274
12275 /* [AS] Try to extract PV info from PGN comment */
12276 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12277 char *GetInfoFromComment( int index, char * text )
12278 {
12279     char * sep = text;
12280
12281     if( text != NULL && index > 0 ) {
12282         int score = 0;
12283         int depth = 0;
12284         int time = -1, sec = 0, deci;
12285         char * s_eval = FindStr( text, "[%eval " );
12286         char * s_emt = FindStr( text, "[%emt " );
12287
12288         if( s_eval != NULL || s_emt != NULL ) {
12289             /* New style */
12290             char delim;
12291
12292             if( s_eval != NULL ) {
12293                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12294                     return text;
12295                 }
12296
12297                 if( delim != ']' ) {
12298                     return text;
12299                 }
12300             }
12301
12302             if( s_emt != NULL ) {
12303             }
12304                 return text;
12305         }
12306         else {
12307             /* We expect something like: [+|-]nnn.nn/dd */
12308             int score_lo = 0;
12309
12310             if(*text != '{') return text; // [HGM] braces: must be normal comment
12311
12312             sep = strchr( text, '/' );
12313             if( sep == NULL || sep < (text+4) ) {
12314                 return text;
12315             }
12316
12317             time = -1; sec = -1; deci = -1;
12318             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12319                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12320                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12321                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12322                 return text;
12323             }
12324
12325             if( score_lo < 0 || score_lo >= 100 ) {
12326                 return text;
12327             }
12328
12329             if(sec >= 0) time = 600*time + 10*sec; else
12330             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12331
12332             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12333
12334             /* [HGM] PV time: now locate end of PV info */
12335             while( *++sep >= '0' && *sep <= '9'); // strip depth
12336             if(time >= 0)
12337             while( *++sep >= '0' && *sep <= '9'); // strip time
12338             if(sec >= 0)
12339             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12340             if(deci >= 0)
12341             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12342             while(*sep == ' ') sep++;
12343         }
12344
12345         if( depth <= 0 ) {
12346             return text;
12347         }
12348
12349         if( time < 0 ) {
12350             time = -1;
12351         }
12352
12353         pvInfoList[index-1].depth = depth;
12354         pvInfoList[index-1].score = score;
12355         pvInfoList[index-1].time  = 10*time; // centi-sec
12356         if(*sep == '}') *sep = 0; else *--sep = '{';
12357     }
12358     return sep;
12359 }
12360
12361 void
12362 SendToProgram(message, cps)
12363      char *message;
12364      ChessProgramState *cps;
12365 {
12366     int count, outCount, error;
12367     char buf[MSG_SIZ];
12368
12369     if (cps->pr == NULL) return;
12370     Attention(cps);
12371     
12372     if (appData.debugMode) {
12373         TimeMark now;
12374         GetTimeMark(&now);
12375         fprintf(debugFP, "%ld >%-6s: %s", 
12376                 SubtractTimeMarks(&now, &programStartTime),
12377                 cps->which, message);
12378     }
12379     
12380     count = strlen(message);
12381     outCount = OutputToProcess(cps->pr, message, count, &error);
12382     if (outCount < count && !exiting 
12383                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12384         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12385         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12386             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12387                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12388                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12389             } else {
12390                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12391             }
12392             gameInfo.resultDetails = buf;
12393         }
12394         DisplayFatalError(buf, error, 1);
12395     }
12396 }
12397
12398 void
12399 ReceiveFromProgram(isr, closure, message, count, error)
12400      InputSourceRef isr;
12401      VOIDSTAR closure;
12402      char *message;
12403      int count;
12404      int error;
12405 {
12406     char *end_str;
12407     char buf[MSG_SIZ];
12408     ChessProgramState *cps = (ChessProgramState *)closure;
12409
12410     if (isr != cps->isr) return; /* Killed intentionally */
12411     if (count <= 0) {
12412         if (count == 0) {
12413             sprintf(buf,
12414                     _("Error: %s chess program (%s) exited unexpectedly"),
12415                     cps->which, cps->program);
12416         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12417                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12418                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12419                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12420                 } else {
12421                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12422                 }
12423                 gameInfo.resultDetails = buf;
12424             }
12425             RemoveInputSource(cps->isr);
12426             DisplayFatalError(buf, 0, 1);
12427         } else {
12428             sprintf(buf,
12429                     _("Error reading from %s chess program (%s)"),
12430                     cps->which, cps->program);
12431             RemoveInputSource(cps->isr);
12432
12433             /* [AS] Program is misbehaving badly... kill it */
12434             if( count == -2 ) {
12435                 DestroyChildProcess( cps->pr, 9 );
12436                 cps->pr = NoProc;
12437             }
12438
12439             DisplayFatalError(buf, error, 1);
12440         }
12441         return;
12442     }
12443     
12444     if ((end_str = strchr(message, '\r')) != NULL)
12445       *end_str = NULLCHAR;
12446     if ((end_str = strchr(message, '\n')) != NULL)
12447       *end_str = NULLCHAR;
12448     
12449     if (appData.debugMode) {
12450         TimeMark now; int print = 1;
12451         char *quote = ""; char c; int i;
12452
12453         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12454                 char start = message[0];
12455                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12456                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12457                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12458                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12459                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12460                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12461                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12462                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12463                         { quote = "# "; print = (appData.engineComments == 2); }
12464                 message[0] = start; // restore original message
12465         }
12466         if(print) {
12467                 GetTimeMark(&now);
12468                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12469                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12470                         quote,
12471                         message);
12472         }
12473     }
12474
12475     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12476     if (appData.icsEngineAnalyze) {
12477         if (strstr(message, "whisper") != NULL ||
12478              strstr(message, "kibitz") != NULL || 
12479             strstr(message, "tellics") != NULL) return;
12480     }
12481
12482     HandleMachineMove(message, cps);
12483 }
12484
12485
12486 void
12487 SendTimeControl(cps, mps, tc, inc, sd, st)
12488      ChessProgramState *cps;
12489      int mps, inc, sd, st;
12490      long tc;
12491 {
12492     char buf[MSG_SIZ];
12493     int seconds;
12494
12495     if( timeControl_2 > 0 ) {
12496         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12497             tc = timeControl_2;
12498         }
12499     }
12500     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12501     inc /= cps->timeOdds;
12502     st  /= cps->timeOdds;
12503
12504     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12505
12506     if (st > 0) {
12507       /* Set exact time per move, normally using st command */
12508       if (cps->stKludge) {
12509         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12510         seconds = st % 60;
12511         if (seconds == 0) {
12512           sprintf(buf, "level 1 %d\n", st/60);
12513         } else {
12514           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12515         }
12516       } else {
12517         sprintf(buf, "st %d\n", st);
12518       }
12519     } else {
12520       /* Set conventional or incremental time control, using level command */
12521       if (seconds == 0) {
12522         /* Note old gnuchess bug -- minutes:seconds used to not work.
12523            Fixed in later versions, but still avoid :seconds
12524            when seconds is 0. */
12525         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12526       } else {
12527         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12528                 seconds, inc/1000);
12529       }
12530     }
12531     SendToProgram(buf, cps);
12532
12533     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12534     /* Orthogonally, limit search to given depth */
12535     if (sd > 0) {
12536       if (cps->sdKludge) {
12537         sprintf(buf, "depth\n%d\n", sd);
12538       } else {
12539         sprintf(buf, "sd %d\n", sd);
12540       }
12541       SendToProgram(buf, cps);
12542     }
12543
12544     if(cps->nps > 0) { /* [HGM] nps */
12545         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12546         else {
12547                 sprintf(buf, "nps %d\n", cps->nps);
12548               SendToProgram(buf, cps);
12549         }
12550     }
12551 }
12552
12553 ChessProgramState *WhitePlayer()
12554 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12555 {
12556     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12557        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12558         return &second;
12559     return &first;
12560 }
12561
12562 void
12563 SendTimeRemaining(cps, machineWhite)
12564      ChessProgramState *cps;
12565      int /*boolean*/ machineWhite;
12566 {
12567     char message[MSG_SIZ];
12568     long time, otime;
12569
12570     /* Note: this routine must be called when the clocks are stopped
12571        or when they have *just* been set or switched; otherwise
12572        it will be off by the time since the current tick started.
12573     */
12574     if (machineWhite) {
12575         time = whiteTimeRemaining / 10;
12576         otime = blackTimeRemaining / 10;
12577     } else {
12578         time = blackTimeRemaining / 10;
12579         otime = whiteTimeRemaining / 10;
12580     }
12581     /* [HGM] translate opponent's time by time-odds factor */
12582     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12583     if (appData.debugMode) {
12584         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12585     }
12586
12587     if (time <= 0) time = 1;
12588     if (otime <= 0) otime = 1;
12589     
12590     sprintf(message, "time %ld\n", time);
12591     SendToProgram(message, cps);
12592
12593     sprintf(message, "otim %ld\n", otime);
12594     SendToProgram(message, cps);
12595 }
12596
12597 int
12598 BoolFeature(p, name, loc, cps)
12599      char **p;
12600      char *name;
12601      int *loc;
12602      ChessProgramState *cps;
12603 {
12604   char buf[MSG_SIZ];
12605   int len = strlen(name);
12606   int val;
12607   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12608     (*p) += len + 1;
12609     sscanf(*p, "%d", &val);
12610     *loc = (val != 0);
12611     while (**p && **p != ' ') (*p)++;
12612     sprintf(buf, "accepted %s\n", name);
12613     SendToProgram(buf, cps);
12614     return TRUE;
12615   }
12616   return FALSE;
12617 }
12618
12619 int
12620 IntFeature(p, name, loc, cps)
12621      char **p;
12622      char *name;
12623      int *loc;
12624      ChessProgramState *cps;
12625 {
12626   char buf[MSG_SIZ];
12627   int len = strlen(name);
12628   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12629     (*p) += len + 1;
12630     sscanf(*p, "%d", loc);
12631     while (**p && **p != ' ') (*p)++;
12632     sprintf(buf, "accepted %s\n", name);
12633     SendToProgram(buf, cps);
12634     return TRUE;
12635   }
12636   return FALSE;
12637 }
12638
12639 int
12640 StringFeature(p, name, loc, cps)
12641      char **p;
12642      char *name;
12643      char loc[];
12644      ChessProgramState *cps;
12645 {
12646   char buf[MSG_SIZ];
12647   int len = strlen(name);
12648   if (strncmp((*p), name, len) == 0
12649       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12650     (*p) += len + 2;
12651     sscanf(*p, "%[^\"]", loc);
12652     while (**p && **p != '\"') (*p)++;
12653     if (**p == '\"') (*p)++;
12654     sprintf(buf, "accepted %s\n", name);
12655     SendToProgram(buf, cps);
12656     return TRUE;
12657   }
12658   return FALSE;
12659 }
12660
12661 int 
12662 ParseOption(Option *opt, ChessProgramState *cps)
12663 // [HGM] options: process the string that defines an engine option, and determine
12664 // name, type, default value, and allowed value range
12665 {
12666         char *p, *q, buf[MSG_SIZ];
12667         int n, min = (-1)<<31, max = 1<<31, def;
12668
12669         if(p = strstr(opt->name, " -spin ")) {
12670             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12671             if(max < min) max = min; // enforce consistency
12672             if(def < min) def = min;
12673             if(def > max) def = max;
12674             opt->value = def;
12675             opt->min = min;
12676             opt->max = max;
12677             opt->type = Spin;
12678         } else if((p = strstr(opt->name, " -slider "))) {
12679             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12680             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12681             if(max < min) max = min; // enforce consistency
12682             if(def < min) def = min;
12683             if(def > max) def = max;
12684             opt->value = def;
12685             opt->min = min;
12686             opt->max = max;
12687             opt->type = Spin; // Slider;
12688         } else if((p = strstr(opt->name, " -string "))) {
12689             opt->textValue = p+9;
12690             opt->type = TextBox;
12691         } else if((p = strstr(opt->name, " -file "))) {
12692             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12693             opt->textValue = p+7;
12694             opt->type = TextBox; // FileName;
12695         } else if((p = strstr(opt->name, " -path "))) {
12696             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12697             opt->textValue = p+7;
12698             opt->type = TextBox; // PathName;
12699         } else if(p = strstr(opt->name, " -check ")) {
12700             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12701             opt->value = (def != 0);
12702             opt->type = CheckBox;
12703         } else if(p = strstr(opt->name, " -combo ")) {
12704             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12705             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12706             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12707             opt->value = n = 0;
12708             while(q = StrStr(q, " /// ")) {
12709                 n++; *q = 0;    // count choices, and null-terminate each of them
12710                 q += 5;
12711                 if(*q == '*') { // remember default, which is marked with * prefix
12712                     q++;
12713                     opt->value = n;
12714                 }
12715                 cps->comboList[cps->comboCnt++] = q;
12716             }
12717             cps->comboList[cps->comboCnt++] = NULL;
12718             opt->max = n + 1;
12719             opt->type = ComboBox;
12720         } else if(p = strstr(opt->name, " -button")) {
12721             opt->type = Button;
12722         } else if(p = strstr(opt->name, " -save")) {
12723             opt->type = SaveButton;
12724         } else return FALSE;
12725         *p = 0; // terminate option name
12726         // now look if the command-line options define a setting for this engine option.
12727         if(cps->optionSettings && cps->optionSettings[0])
12728             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12729         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12730                 sprintf(buf, "option %s", p);
12731                 if(p = strstr(buf, ",")) *p = 0;
12732                 strcat(buf, "\n");
12733                 SendToProgram(buf, cps);
12734         }
12735         return TRUE;
12736 }
12737
12738 void
12739 FeatureDone(cps, val)
12740      ChessProgramState* cps;
12741      int val;
12742 {
12743   DelayedEventCallback cb = GetDelayedEvent();
12744   if ((cb == InitBackEnd3 && cps == &first) ||
12745       (cb == TwoMachinesEventIfReady && cps == &second)) {
12746     CancelDelayedEvent();
12747     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12748   }
12749   cps->initDone = val;
12750 }
12751
12752 /* Parse feature command from engine */
12753 void
12754 ParseFeatures(args, cps)
12755      char* args;
12756      ChessProgramState *cps;  
12757 {
12758   char *p = args;
12759   char *q;
12760   int val;
12761   char buf[MSG_SIZ];
12762
12763   for (;;) {
12764     while (*p == ' ') p++;
12765     if (*p == NULLCHAR) return;
12766
12767     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12768     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12769     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12770     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12771     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12772     if (BoolFeature(&p, "reuse", &val, cps)) {
12773       /* Engine can disable reuse, but can't enable it if user said no */
12774       if (!val) cps->reuse = FALSE;
12775       continue;
12776     }
12777     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12778     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12779       if (gameMode == TwoMachinesPlay) {
12780         DisplayTwoMachinesTitle();
12781       } else {
12782         DisplayTitle("");
12783       }
12784       continue;
12785     }
12786     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12787     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12788     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12789     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12790     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12791     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12792     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12793     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12794     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12795     if (IntFeature(&p, "done", &val, cps)) {
12796       FeatureDone(cps, val);
12797       continue;
12798     }
12799     /* Added by Tord: */
12800     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12801     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12802     /* End of additions by Tord */
12803
12804     /* [HGM] added features: */
12805     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12806     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12807     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12808     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12809     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12810     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12811     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12812         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12813             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12814             SendToProgram(buf, cps);
12815             continue;
12816         }
12817         if(cps->nrOptions >= MAX_OPTIONS) {
12818             cps->nrOptions--;
12819             sprintf(buf, "%s engine has too many options\n", cps->which);
12820             DisplayError(buf, 0);
12821         }
12822         continue;
12823     }
12824     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12825     /* End of additions by HGM */
12826
12827     /* unknown feature: complain and skip */
12828     q = p;
12829     while (*q && *q != '=') q++;
12830     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12831     SendToProgram(buf, cps);
12832     p = q;
12833     if (*p == '=') {
12834       p++;
12835       if (*p == '\"') {
12836         p++;
12837         while (*p && *p != '\"') p++;
12838         if (*p == '\"') p++;
12839       } else {
12840         while (*p && *p != ' ') p++;
12841       }
12842     }
12843   }
12844
12845 }
12846
12847 void
12848 PeriodicUpdatesEvent(newState)
12849      int newState;
12850 {
12851     if (newState == appData.periodicUpdates)
12852       return;
12853
12854     appData.periodicUpdates=newState;
12855
12856     /* Display type changes, so update it now */
12857 //    DisplayAnalysis();
12858
12859     /* Get the ball rolling again... */
12860     if (newState) {
12861         AnalysisPeriodicEvent(1);
12862         StartAnalysisClock();
12863     }
12864 }
12865
12866 void
12867 PonderNextMoveEvent(newState)
12868      int newState;
12869 {
12870     if (newState == appData.ponderNextMove) return;
12871     if (gameMode == EditPosition) EditPositionDone(TRUE);
12872     if (newState) {
12873         SendToProgram("hard\n", &first);
12874         if (gameMode == TwoMachinesPlay) {
12875             SendToProgram("hard\n", &second);
12876         }
12877     } else {
12878         SendToProgram("easy\n", &first);
12879         thinkOutput[0] = NULLCHAR;
12880         if (gameMode == TwoMachinesPlay) {
12881             SendToProgram("easy\n", &second);
12882         }
12883     }
12884     appData.ponderNextMove = newState;
12885 }
12886
12887 void
12888 NewSettingEvent(option, command, value)
12889      char *command;
12890      int option, value;
12891 {
12892     char buf[MSG_SIZ];
12893
12894     if (gameMode == EditPosition) EditPositionDone(TRUE);
12895     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12896     SendToProgram(buf, &first);
12897     if (gameMode == TwoMachinesPlay) {
12898         SendToProgram(buf, &second);
12899     }
12900 }
12901
12902 void
12903 ShowThinkingEvent()
12904 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12905 {
12906     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12907     int newState = appData.showThinking
12908         // [HGM] thinking: other features now need thinking output as well
12909         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12910     
12911     if (oldState == newState) return;
12912     oldState = newState;
12913     if (gameMode == EditPosition) EditPositionDone(TRUE);
12914     if (oldState) {
12915         SendToProgram("post\n", &first);
12916         if (gameMode == TwoMachinesPlay) {
12917             SendToProgram("post\n", &second);
12918         }
12919     } else {
12920         SendToProgram("nopost\n", &first);
12921         thinkOutput[0] = NULLCHAR;
12922         if (gameMode == TwoMachinesPlay) {
12923             SendToProgram("nopost\n", &second);
12924         }
12925     }
12926 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12927 }
12928
12929 void
12930 AskQuestionEvent(title, question, replyPrefix, which)
12931      char *title; char *question; char *replyPrefix; char *which;
12932 {
12933   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12934   if (pr == NoProc) return;
12935   AskQuestion(title, question, replyPrefix, pr);
12936 }
12937
12938 void
12939 DisplayMove(moveNumber)
12940      int moveNumber;
12941 {
12942     char message[MSG_SIZ];
12943     char res[MSG_SIZ];
12944     char cpThinkOutput[MSG_SIZ];
12945
12946     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12947     
12948     if (moveNumber == forwardMostMove - 1 || 
12949         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12950
12951         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12952
12953         if (strchr(cpThinkOutput, '\n')) {
12954             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12955         }
12956     } else {
12957         *cpThinkOutput = NULLCHAR;
12958     }
12959
12960     /* [AS] Hide thinking from human user */
12961     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12962         *cpThinkOutput = NULLCHAR;
12963         if( thinkOutput[0] != NULLCHAR ) {
12964             int i;
12965
12966             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12967                 cpThinkOutput[i] = '.';
12968             }
12969             cpThinkOutput[i] = NULLCHAR;
12970             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12971         }
12972     }
12973
12974     if (moveNumber == forwardMostMove - 1 &&
12975         gameInfo.resultDetails != NULL) {
12976         if (gameInfo.resultDetails[0] == NULLCHAR) {
12977             sprintf(res, " %s", PGNResult(gameInfo.result));
12978         } else {
12979             sprintf(res, " {%s} %s",
12980                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12981         }
12982     } else {
12983         res[0] = NULLCHAR;
12984     }
12985
12986     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12987         DisplayMessage(res, cpThinkOutput);
12988     } else {
12989         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12990                 WhiteOnMove(moveNumber) ? " " : ".. ",
12991                 parseList[moveNumber], res);
12992         DisplayMessage(message, cpThinkOutput);
12993     }
12994 }
12995
12996 void
12997 DisplayComment(moveNumber, text)
12998      int moveNumber;
12999      char *text;
13000 {
13001     char title[MSG_SIZ];
13002     char buf[8000]; // comment can be long!
13003     int score, depth;
13004     
13005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13006       strcpy(title, "Comment");
13007     } else {
13008       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13009               WhiteOnMove(moveNumber) ? " " : ".. ",
13010               parseList[moveNumber]);
13011     }
13012     // [HGM] PV info: display PV info together with (or as) comment
13013     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13014       if(text == NULL) text = "";                                           
13015       score = pvInfoList[moveNumber].score;
13016       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13017               depth, (pvInfoList[moveNumber].time+50)/100, text);
13018       text = buf;
13019     }
13020     if (text != NULL && (appData.autoDisplayComment || commentUp))
13021         CommentPopUp(title, text);
13022 }
13023
13024 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13025  * might be busy thinking or pondering.  It can be omitted if your
13026  * gnuchess is configured to stop thinking immediately on any user
13027  * input.  However, that gnuchess feature depends on the FIONREAD
13028  * ioctl, which does not work properly on some flavors of Unix.
13029  */
13030 void
13031 Attention(cps)
13032      ChessProgramState *cps;
13033 {
13034 #if ATTENTION
13035     if (!cps->useSigint) return;
13036     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13037     switch (gameMode) {
13038       case MachinePlaysWhite:
13039       case MachinePlaysBlack:
13040       case TwoMachinesPlay:
13041       case IcsPlayingWhite:
13042       case IcsPlayingBlack:
13043       case AnalyzeMode:
13044       case AnalyzeFile:
13045         /* Skip if we know it isn't thinking */
13046         if (!cps->maybeThinking) return;
13047         if (appData.debugMode)
13048           fprintf(debugFP, "Interrupting %s\n", cps->which);
13049         InterruptChildProcess(cps->pr);
13050         cps->maybeThinking = FALSE;
13051         break;
13052       default:
13053         break;
13054     }
13055 #endif /*ATTENTION*/
13056 }
13057
13058 int
13059 CheckFlags()
13060 {
13061     if (whiteTimeRemaining <= 0) {
13062         if (!whiteFlag) {
13063             whiteFlag = TRUE;
13064             if (appData.icsActive) {
13065                 if (appData.autoCallFlag &&
13066                     gameMode == IcsPlayingBlack && !blackFlag) {
13067                   SendToICS(ics_prefix);
13068                   SendToICS("flag\n");
13069                 }
13070             } else {
13071                 if (blackFlag) {
13072                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13073                 } else {
13074                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13075                     if (appData.autoCallFlag) {
13076                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13077                         return TRUE;
13078                     }
13079                 }
13080             }
13081         }
13082     }
13083     if (blackTimeRemaining <= 0) {
13084         if (!blackFlag) {
13085             blackFlag = TRUE;
13086             if (appData.icsActive) {
13087                 if (appData.autoCallFlag &&
13088                     gameMode == IcsPlayingWhite && !whiteFlag) {
13089                   SendToICS(ics_prefix);
13090                   SendToICS("flag\n");
13091                 }
13092             } else {
13093                 if (whiteFlag) {
13094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13095                 } else {
13096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13097                     if (appData.autoCallFlag) {
13098                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13099                         return TRUE;
13100                     }
13101                 }
13102             }
13103         }
13104     }
13105     return FALSE;
13106 }
13107
13108 void
13109 CheckTimeControl()
13110 {
13111     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13112         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13113
13114     /*
13115      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13116      */
13117     if ( !WhiteOnMove(forwardMostMove) )
13118         /* White made time control */
13119         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13120         /* [HGM] time odds: correct new time quota for time odds! */
13121                                             / WhitePlayer()->timeOdds;
13122       else
13123         /* Black made time control */
13124         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13125                                             / WhitePlayer()->other->timeOdds;
13126 }
13127
13128 void
13129 DisplayBothClocks()
13130 {
13131     int wom = gameMode == EditPosition ?
13132       !blackPlaysFirst : WhiteOnMove(currentMove);
13133     DisplayWhiteClock(whiteTimeRemaining, wom);
13134     DisplayBlackClock(blackTimeRemaining, !wom);
13135 }
13136
13137
13138 /* Timekeeping seems to be a portability nightmare.  I think everyone
13139    has ftime(), but I'm really not sure, so I'm including some ifdefs
13140    to use other calls if you don't.  Clocks will be less accurate if
13141    you have neither ftime nor gettimeofday.
13142 */
13143
13144 /* VS 2008 requires the #include outside of the function */
13145 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13146 #include <sys/timeb.h>
13147 #endif
13148
13149 /* Get the current time as a TimeMark */
13150 void
13151 GetTimeMark(tm)
13152      TimeMark *tm;
13153 {
13154 #if HAVE_GETTIMEOFDAY
13155
13156     struct timeval timeVal;
13157     struct timezone timeZone;
13158
13159     gettimeofday(&timeVal, &timeZone);
13160     tm->sec = (long) timeVal.tv_sec; 
13161     tm->ms = (int) (timeVal.tv_usec / 1000L);
13162
13163 #else /*!HAVE_GETTIMEOFDAY*/
13164 #if HAVE_FTIME
13165
13166 // include <sys/timeb.h> / moved to just above start of function
13167     struct timeb timeB;
13168
13169     ftime(&timeB);
13170     tm->sec = (long) timeB.time;
13171     tm->ms = (int) timeB.millitm;
13172
13173 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13174     tm->sec = (long) time(NULL);
13175     tm->ms = 0;
13176 #endif
13177 #endif
13178 }
13179
13180 /* Return the difference in milliseconds between two
13181    time marks.  We assume the difference will fit in a long!
13182 */
13183 long
13184 SubtractTimeMarks(tm2, tm1)
13185      TimeMark *tm2, *tm1;
13186 {
13187     return 1000L*(tm2->sec - tm1->sec) +
13188            (long) (tm2->ms - tm1->ms);
13189 }
13190
13191
13192 /*
13193  * Code to manage the game clocks.
13194  *
13195  * In tournament play, black starts the clock and then white makes a move.
13196  * We give the human user a slight advantage if he is playing white---the
13197  * clocks don't run until he makes his first move, so it takes zero time.
13198  * Also, we don't account for network lag, so we could get out of sync
13199  * with GNU Chess's clock -- but then, referees are always right.  
13200  */
13201
13202 static TimeMark tickStartTM;
13203 static long intendedTickLength;
13204
13205 long
13206 NextTickLength(timeRemaining)
13207      long timeRemaining;
13208 {
13209     long nominalTickLength, nextTickLength;
13210
13211     if (timeRemaining > 0L && timeRemaining <= 10000L)
13212       nominalTickLength = 100L;
13213     else
13214       nominalTickLength = 1000L;
13215     nextTickLength = timeRemaining % nominalTickLength;
13216     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13217
13218     return nextTickLength;
13219 }
13220
13221 /* Adjust clock one minute up or down */
13222 void
13223 AdjustClock(Boolean which, int dir)
13224 {
13225     if(which) blackTimeRemaining += 60000*dir;
13226     else      whiteTimeRemaining += 60000*dir;
13227     DisplayBothClocks();
13228 }
13229
13230 /* Stop clocks and reset to a fresh time control */
13231 void
13232 ResetClocks() 
13233 {
13234     (void) StopClockTimer();
13235     if (appData.icsActive) {
13236         whiteTimeRemaining = blackTimeRemaining = 0;
13237     } else if (searchTime) {
13238         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13239         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13240     } else { /* [HGM] correct new time quote for time odds */
13241         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13242         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13243     }
13244     if (whiteFlag || blackFlag) {
13245         DisplayTitle("");
13246         whiteFlag = blackFlag = FALSE;
13247     }
13248     DisplayBothClocks();
13249 }
13250
13251 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13252
13253 /* Decrement running clock by amount of time that has passed */
13254 void
13255 DecrementClocks()
13256 {
13257     long timeRemaining;
13258     long lastTickLength, fudge;
13259     TimeMark now;
13260
13261     if (!appData.clockMode) return;
13262     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13263         
13264     GetTimeMark(&now);
13265
13266     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13267
13268     /* Fudge if we woke up a little too soon */
13269     fudge = intendedTickLength - lastTickLength;
13270     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13271
13272     if (WhiteOnMove(forwardMostMove)) {
13273         if(whiteNPS >= 0) lastTickLength = 0;
13274         timeRemaining = whiteTimeRemaining -= lastTickLength;
13275         DisplayWhiteClock(whiteTimeRemaining - fudge,
13276                           WhiteOnMove(currentMove));
13277     } else {
13278         if(blackNPS >= 0) lastTickLength = 0;
13279         timeRemaining = blackTimeRemaining -= lastTickLength;
13280         DisplayBlackClock(blackTimeRemaining - fudge,
13281                           !WhiteOnMove(currentMove));
13282     }
13283
13284     if (CheckFlags()) return;
13285         
13286     tickStartTM = now;
13287     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13288     StartClockTimer(intendedTickLength);
13289
13290     /* if the time remaining has fallen below the alarm threshold, sound the
13291      * alarm. if the alarm has sounded and (due to a takeback or time control
13292      * with increment) the time remaining has increased to a level above the
13293      * threshold, reset the alarm so it can sound again. 
13294      */
13295     
13296     if (appData.icsActive && appData.icsAlarm) {
13297
13298         /* make sure we are dealing with the user's clock */
13299         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13300                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13301            )) return;
13302
13303         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13304             alarmSounded = FALSE;
13305         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13306             PlayAlarmSound();
13307             alarmSounded = TRUE;
13308         }
13309     }
13310 }
13311
13312
13313 /* A player has just moved, so stop the previously running
13314    clock and (if in clock mode) start the other one.
13315    We redisplay both clocks in case we're in ICS mode, because
13316    ICS gives us an update to both clocks after every move.
13317    Note that this routine is called *after* forwardMostMove
13318    is updated, so the last fractional tick must be subtracted
13319    from the color that is *not* on move now.
13320 */
13321 void
13322 SwitchClocks()
13323 {
13324     long lastTickLength;
13325     TimeMark now;
13326     int flagged = FALSE;
13327
13328     GetTimeMark(&now);
13329
13330     if (StopClockTimer() && appData.clockMode) {
13331         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13332         if (WhiteOnMove(forwardMostMove)) {
13333             if(blackNPS >= 0) lastTickLength = 0;
13334             blackTimeRemaining -= lastTickLength;
13335            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13336 //         if(pvInfoList[forwardMostMove-1].time == -1)
13337                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13338                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13339         } else {
13340            if(whiteNPS >= 0) lastTickLength = 0;
13341            whiteTimeRemaining -= lastTickLength;
13342            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13343 //         if(pvInfoList[forwardMostMove-1].time == -1)
13344                  pvInfoList[forwardMostMove-1].time = 
13345                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13346         }
13347         flagged = CheckFlags();
13348     }
13349     CheckTimeControl();
13350
13351     if (flagged || !appData.clockMode) return;
13352
13353     switch (gameMode) {
13354       case MachinePlaysBlack:
13355       case MachinePlaysWhite:
13356       case BeginningOfGame:
13357         if (pausing) return;
13358         break;
13359
13360       case EditGame:
13361       case PlayFromGameFile:
13362       case IcsExamining:
13363         return;
13364
13365       default:
13366         break;
13367     }
13368
13369     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13370         if(WhiteOnMove(forwardMostMove))
13371              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13372         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13373     }
13374
13375     tickStartTM = now;
13376     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13377       whiteTimeRemaining : blackTimeRemaining);
13378     StartClockTimer(intendedTickLength);
13379 }
13380         
13381
13382 /* Stop both clocks */
13383 void
13384 StopClocks()
13385 {       
13386     long lastTickLength;
13387     TimeMark now;
13388
13389     if (!StopClockTimer()) return;
13390     if (!appData.clockMode) return;
13391
13392     GetTimeMark(&now);
13393
13394     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13395     if (WhiteOnMove(forwardMostMove)) {
13396         if(whiteNPS >= 0) lastTickLength = 0;
13397         whiteTimeRemaining -= lastTickLength;
13398         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13399     } else {
13400         if(blackNPS >= 0) lastTickLength = 0;
13401         blackTimeRemaining -= lastTickLength;
13402         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13403     }
13404     CheckFlags();
13405 }
13406         
13407 /* Start clock of player on move.  Time may have been reset, so
13408    if clock is already running, stop and restart it. */
13409 void
13410 StartClocks()
13411 {
13412     (void) StopClockTimer(); /* in case it was running already */
13413     DisplayBothClocks();
13414     if (CheckFlags()) return;
13415
13416     if (!appData.clockMode) return;
13417     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13418
13419     GetTimeMark(&tickStartTM);
13420     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13421       whiteTimeRemaining : blackTimeRemaining);
13422
13423    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13424     whiteNPS = blackNPS = -1; 
13425     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13426        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13427         whiteNPS = first.nps;
13428     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13429        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13430         blackNPS = first.nps;
13431     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13432         whiteNPS = second.nps;
13433     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13434         blackNPS = second.nps;
13435     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13436
13437     StartClockTimer(intendedTickLength);
13438 }
13439
13440 char *
13441 TimeString(ms)
13442      long ms;
13443 {
13444     long second, minute, hour, day;
13445     char *sign = "";
13446     static char buf[32];
13447     
13448     if (ms > 0 && ms <= 9900) {
13449       /* convert milliseconds to tenths, rounding up */
13450       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13451
13452       sprintf(buf, " %03.1f ", tenths/10.0);
13453       return buf;
13454     }
13455
13456     /* convert milliseconds to seconds, rounding up */
13457     /* use floating point to avoid strangeness of integer division
13458        with negative dividends on many machines */
13459     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13460
13461     if (second < 0) {
13462         sign = "-";
13463         second = -second;
13464     }
13465     
13466     day = second / (60 * 60 * 24);
13467     second = second % (60 * 60 * 24);
13468     hour = second / (60 * 60);
13469     second = second % (60 * 60);
13470     minute = second / 60;
13471     second = second % 60;
13472     
13473     if (day > 0)
13474       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13475               sign, day, hour, minute, second);
13476     else if (hour > 0)
13477       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13478     else
13479       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13480     
13481     return buf;
13482 }
13483
13484
13485 /*
13486  * This is necessary because some C libraries aren't ANSI C compliant yet.
13487  */
13488 char *
13489 StrStr(string, match)
13490      char *string, *match;
13491 {
13492     int i, length;
13493     
13494     length = strlen(match);
13495     
13496     for (i = strlen(string) - length; i >= 0; i--, string++)
13497       if (!strncmp(match, string, length))
13498         return string;
13499     
13500     return NULL;
13501 }
13502
13503 char *
13504 StrCaseStr(string, match)
13505      char *string, *match;
13506 {
13507     int i, j, length;
13508     
13509     length = strlen(match);
13510     
13511     for (i = strlen(string) - length; i >= 0; i--, string++) {
13512         for (j = 0; j < length; j++) {
13513             if (ToLower(match[j]) != ToLower(string[j]))
13514               break;
13515         }
13516         if (j == length) return string;
13517     }
13518
13519     return NULL;
13520 }
13521
13522 #ifndef _amigados
13523 int
13524 StrCaseCmp(s1, s2)
13525      char *s1, *s2;
13526 {
13527     char c1, c2;
13528     
13529     for (;;) {
13530         c1 = ToLower(*s1++);
13531         c2 = ToLower(*s2++);
13532         if (c1 > c2) return 1;
13533         if (c1 < c2) return -1;
13534         if (c1 == NULLCHAR) return 0;
13535     }
13536 }
13537
13538
13539 int
13540 ToLower(c)
13541      int c;
13542 {
13543     return isupper(c) ? tolower(c) : c;
13544 }
13545
13546
13547 int
13548 ToUpper(c)
13549      int c;
13550 {
13551     return islower(c) ? toupper(c) : c;
13552 }
13553 #endif /* !_amigados    */
13554
13555 char *
13556 StrSave(s)
13557      char *s;
13558 {
13559     char *ret;
13560
13561     if ((ret = (char *) malloc(strlen(s) + 1))) {
13562         strcpy(ret, s);
13563     }
13564     return ret;
13565 }
13566
13567 char *
13568 StrSavePtr(s, savePtr)
13569      char *s, **savePtr;
13570 {
13571     if (*savePtr) {
13572         free(*savePtr);
13573     }
13574     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13575         strcpy(*savePtr, s);
13576     }
13577     return(*savePtr);
13578 }
13579
13580 char *
13581 PGNDate()
13582 {
13583     time_t clock;
13584     struct tm *tm;
13585     char buf[MSG_SIZ];
13586
13587     clock = time((time_t *)NULL);
13588     tm = localtime(&clock);
13589     sprintf(buf, "%04d.%02d.%02d",
13590             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13591     return StrSave(buf);
13592 }
13593
13594
13595 char *
13596 PositionToFEN(move, overrideCastling)
13597      int move;
13598      char *overrideCastling;
13599 {
13600     int i, j, fromX, fromY, toX, toY;
13601     int whiteToPlay;
13602     char buf[128];
13603     char *p, *q;
13604     int emptycount;
13605     ChessSquare piece;
13606
13607     whiteToPlay = (gameMode == EditPosition) ?
13608       !blackPlaysFirst : (move % 2 == 0);
13609     p = buf;
13610
13611     /* Piece placement data */
13612     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13613         emptycount = 0;
13614         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13615             if (boards[move][i][j] == EmptySquare) {
13616                 emptycount++;
13617             } else { ChessSquare piece = boards[move][i][j];
13618                 if (emptycount > 0) {
13619                     if(emptycount<10) /* [HGM] can be >= 10 */
13620                         *p++ = '0' + emptycount;
13621                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13622                     emptycount = 0;
13623                 }
13624                 if(PieceToChar(piece) == '+') {
13625                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13626                     *p++ = '+';
13627                     piece = (ChessSquare)(DEMOTED piece);
13628                 } 
13629                 *p++ = PieceToChar(piece);
13630                 if(p[-1] == '~') {
13631                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13632                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13633                     *p++ = '~';
13634                 }
13635             }
13636         }
13637         if (emptycount > 0) {
13638             if(emptycount<10) /* [HGM] can be >= 10 */
13639                 *p++ = '0' + emptycount;
13640             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13641             emptycount = 0;
13642         }
13643         *p++ = '/';
13644     }
13645     *(p - 1) = ' ';
13646
13647     /* [HGM] print Crazyhouse or Shogi holdings */
13648     if( gameInfo.holdingsWidth ) {
13649         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13650         q = p;
13651         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13652             piece = boards[move][i][BOARD_WIDTH-1];
13653             if( piece != EmptySquare )
13654               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13655                   *p++ = PieceToChar(piece);
13656         }
13657         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13658             piece = boards[move][BOARD_HEIGHT-i-1][0];
13659             if( piece != EmptySquare )
13660               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13661                   *p++ = PieceToChar(piece);
13662         }
13663
13664         if( q == p ) *p++ = '-';
13665         *p++ = ']';
13666         *p++ = ' ';
13667     }
13668
13669     /* Active color */
13670     *p++ = whiteToPlay ? 'w' : 'b';
13671     *p++ = ' ';
13672
13673   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13674     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13675   } else {
13676   if(nrCastlingRights) {
13677      q = p;
13678      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13679        /* [HGM] write directly from rights */
13680            if(boards[move][CASTLING][2] != NoRights &&
13681               boards[move][CASTLING][0] != NoRights   )
13682                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13683            if(boards[move][CASTLING][2] != NoRights &&
13684               boards[move][CASTLING][1] != NoRights   )
13685                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13686            if(boards[move][CASTLING][5] != NoRights &&
13687               boards[move][CASTLING][3] != NoRights   )
13688                 *p++ = boards[move][CASTLING][3] + AAA;
13689            if(boards[move][CASTLING][5] != NoRights &&
13690               boards[move][CASTLING][4] != NoRights   )
13691                 *p++ = boards[move][CASTLING][4] + AAA;
13692      } else {
13693
13694         /* [HGM] write true castling rights */
13695         if( nrCastlingRights == 6 ) {
13696             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13697                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13698             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13699                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13700             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13701                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13702             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13703                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13704         }
13705      }
13706      if (q == p) *p++ = '-'; /* No castling rights */
13707      *p++ = ' ';
13708   }
13709
13710   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13711      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13712     /* En passant target square */
13713     if (move > backwardMostMove) {
13714         fromX = moveList[move - 1][0] - AAA;
13715         fromY = moveList[move - 1][1] - ONE;
13716         toX = moveList[move - 1][2] - AAA;
13717         toY = moveList[move - 1][3] - ONE;
13718         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13719             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13720             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13721             fromX == toX) {
13722             /* 2-square pawn move just happened */
13723             *p++ = toX + AAA;
13724             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13725         } else {
13726             *p++ = '-';
13727         }
13728     } else if(move == backwardMostMove) {
13729         // [HGM] perhaps we should always do it like this, and forget the above?
13730         if((signed char)boards[move][EP_STATUS] >= 0) {
13731             *p++ = boards[move][EP_STATUS] + AAA;
13732             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13733         } else {
13734             *p++ = '-';
13735         }
13736     } else {
13737         *p++ = '-';
13738     }
13739     *p++ = ' ';
13740   }
13741   }
13742
13743     /* [HGM] find reversible plies */
13744     {   int i = 0, j=move;
13745
13746         if (appData.debugMode) { int k;
13747             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13748             for(k=backwardMostMove; k<=forwardMostMove; k++)
13749                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13750
13751         }
13752
13753         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13754         if( j == backwardMostMove ) i += initialRulePlies;
13755         sprintf(p, "%d ", i);
13756         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13757     }
13758     /* Fullmove number */
13759     sprintf(p, "%d", (move / 2) + 1);
13760     
13761     return StrSave(buf);
13762 }
13763
13764 Boolean
13765 ParseFEN(board, blackPlaysFirst, fen)
13766     Board board;
13767      int *blackPlaysFirst;
13768      char *fen;
13769 {
13770     int i, j;
13771     char *p;
13772     int emptycount;
13773     ChessSquare piece;
13774
13775     p = fen;
13776
13777     /* [HGM] by default clear Crazyhouse holdings, if present */
13778     if(gameInfo.holdingsWidth) {
13779        for(i=0; i<BOARD_HEIGHT; i++) {
13780            board[i][0]             = EmptySquare; /* black holdings */
13781            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13782            board[i][1]             = (ChessSquare) 0; /* black counts */
13783            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13784        }
13785     }
13786
13787     /* Piece placement data */
13788     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13789         j = 0;
13790         for (;;) {
13791             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13792                 if (*p == '/') p++;
13793                 emptycount = gameInfo.boardWidth - j;
13794                 while (emptycount--)
13795                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13796                 break;
13797 #if(BOARD_FILES >= 10)
13798             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13799                 p++; emptycount=10;
13800                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13801                 while (emptycount--)
13802                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13803 #endif
13804             } else if (isdigit(*p)) {
13805                 emptycount = *p++ - '0';
13806                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13807                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13808                 while (emptycount--)
13809                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13810             } else if (*p == '+' || isalpha(*p)) {
13811                 if (j >= gameInfo.boardWidth) return FALSE;
13812                 if(*p=='+') {
13813                     piece = CharToPiece(*++p);
13814                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13815                     piece = (ChessSquare) (PROMOTED piece ); p++;
13816                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13817                 } else piece = CharToPiece(*p++);
13818
13819                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13820                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13821                     piece = (ChessSquare) (PROMOTED piece);
13822                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13823                     p++;
13824                 }
13825                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13826             } else {
13827                 return FALSE;
13828             }
13829         }
13830     }
13831     while (*p == '/' || *p == ' ') p++;
13832
13833     /* [HGM] look for Crazyhouse holdings here */
13834     while(*p==' ') p++;
13835     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13836         if(*p == '[') p++;
13837         if(*p == '-' ) *p++; /* empty holdings */ else {
13838             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13839             /* if we would allow FEN reading to set board size, we would   */
13840             /* have to add holdings and shift the board read so far here   */
13841             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13842                 *p++;
13843                 if((int) piece >= (int) BlackPawn ) {
13844                     i = (int)piece - (int)BlackPawn;
13845                     i = PieceToNumber((ChessSquare)i);
13846                     if( i >= gameInfo.holdingsSize ) return FALSE;
13847                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13848                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13849                 } else {
13850                     i = (int)piece - (int)WhitePawn;
13851                     i = PieceToNumber((ChessSquare)i);
13852                     if( i >= gameInfo.holdingsSize ) return FALSE;
13853                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13854                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13855                 }
13856             }
13857         }
13858         if(*p == ']') *p++;
13859     }
13860
13861     while(*p == ' ') p++;
13862
13863     /* Active color */
13864     switch (*p++) {
13865       case 'w':
13866         *blackPlaysFirst = FALSE;
13867         break;
13868       case 'b': 
13869         *blackPlaysFirst = TRUE;
13870         break;
13871       default:
13872         return FALSE;
13873     }
13874
13875     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13876     /* return the extra info in global variiables             */
13877
13878     /* set defaults in case FEN is incomplete */
13879     board[EP_STATUS] = EP_UNKNOWN;
13880     for(i=0; i<nrCastlingRights; i++ ) {
13881         board[CASTLING][i] =
13882             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13883     }   /* assume possible unless obviously impossible */
13884     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13885     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13886     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13887     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13888     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13889     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13890     FENrulePlies = 0;
13891
13892     while(*p==' ') p++;
13893     if(nrCastlingRights) {
13894       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13895           /* castling indicator present, so default becomes no castlings */
13896           for(i=0; i<nrCastlingRights; i++ ) {
13897                  board[CASTLING][i] = NoRights;
13898           }
13899       }
13900       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13901              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13902              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13903              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13904         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13905
13906         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13907             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13908             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13909         }
13910         switch(c) {
13911           case'K':
13912               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13913               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13914               board[CASTLING][2] = whiteKingFile;
13915               break;
13916           case'Q':
13917               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13918               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13919               board[CASTLING][2] = whiteKingFile;
13920               break;
13921           case'k':
13922               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13923               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13924               board[CASTLING][5] = blackKingFile;
13925               break;
13926           case'q':
13927               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13928               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13929               board[CASTLING][5] = blackKingFile;
13930           case '-':
13931               break;
13932           default: /* FRC castlings */
13933               if(c >= 'a') { /* black rights */
13934                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13935                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13936                   if(i == BOARD_RGHT) break;
13937                   board[CASTLING][5] = i;
13938                   c -= AAA;
13939                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13940                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13941                   if(c > i)
13942                       board[CASTLING][3] = c;
13943                   else
13944                       board[CASTLING][4] = c;
13945               } else { /* white rights */
13946                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13947                     if(board[0][i] == WhiteKing) break;
13948                   if(i == BOARD_RGHT) break;
13949                   board[CASTLING][2] = i;
13950                   c -= AAA - 'a' + 'A';
13951                   if(board[0][c] >= WhiteKing) break;
13952                   if(c > i)
13953                       board[CASTLING][0] = c;
13954                   else
13955                       board[CASTLING][1] = c;
13956               }
13957         }
13958       }
13959     if (appData.debugMode) {
13960         fprintf(debugFP, "FEN castling rights:");
13961         for(i=0; i<nrCastlingRights; i++)
13962         fprintf(debugFP, " %d", board[CASTLING][i]);
13963         fprintf(debugFP, "\n");
13964     }
13965
13966       while(*p==' ') p++;
13967     }
13968
13969     /* read e.p. field in games that know e.p. capture */
13970     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13971        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13972       if(*p=='-') {
13973         p++; board[EP_STATUS] = EP_NONE;
13974       } else {
13975          char c = *p++ - AAA;
13976
13977          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13978          if(*p >= '0' && *p <='9') *p++;
13979          board[EP_STATUS] = c;
13980       }
13981     }
13982
13983
13984     if(sscanf(p, "%d", &i) == 1) {
13985         FENrulePlies = i; /* 50-move ply counter */
13986         /* (The move number is still ignored)    */
13987     }
13988
13989     return TRUE;
13990 }
13991       
13992 void
13993 EditPositionPasteFEN(char *fen)
13994 {
13995   if (fen != NULL) {
13996     Board initial_position;
13997
13998     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13999       DisplayError(_("Bad FEN position in clipboard"), 0);
14000       return ;
14001     } else {
14002       int savedBlackPlaysFirst = blackPlaysFirst;
14003       EditPositionEvent();
14004       blackPlaysFirst = savedBlackPlaysFirst;
14005       CopyBoard(boards[0], initial_position);
14006       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14007       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14008       DisplayBothClocks();
14009       DrawPosition(FALSE, boards[currentMove]);
14010     }
14011   }
14012 }
14013
14014 static char cseq[12] = "\\   ";
14015
14016 Boolean set_cont_sequence(char *new_seq)
14017 {
14018     int len;
14019     Boolean ret;
14020
14021     // handle bad attempts to set the sequence
14022         if (!new_seq)
14023                 return 0; // acceptable error - no debug
14024
14025     len = strlen(new_seq);
14026     ret = (len > 0) && (len < sizeof(cseq));
14027     if (ret)
14028         strcpy(cseq, new_seq);
14029     else if (appData.debugMode)
14030         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14031     return ret;
14032 }
14033
14034 /*
14035     reformat a source message so words don't cross the width boundary.  internal
14036     newlines are not removed.  returns the wrapped size (no null character unless
14037     included in source message).  If dest is NULL, only calculate the size required
14038     for the dest buffer.  lp argument indicats line position upon entry, and it's
14039     passed back upon exit.
14040 */
14041 int wrap(char *dest, char *src, int count, int width, int *lp)
14042 {
14043     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14044
14045     cseq_len = strlen(cseq);
14046     old_line = line = *lp;
14047     ansi = len = clen = 0;
14048
14049     for (i=0; i < count; i++)
14050     {
14051         if (src[i] == '\033')
14052             ansi = 1;
14053
14054         // if we hit the width, back up
14055         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14056         {
14057             // store i & len in case the word is too long
14058             old_i = i, old_len = len;
14059
14060             // find the end of the last word
14061             while (i && src[i] != ' ' && src[i] != '\n')
14062             {
14063                 i--;
14064                 len--;
14065             }
14066
14067             // word too long?  restore i & len before splitting it
14068             if ((old_i-i+clen) >= width)
14069             {
14070                 i = old_i;
14071                 len = old_len;
14072             }
14073
14074             // extra space?
14075             if (i && src[i-1] == ' ')
14076                 len--;
14077
14078             if (src[i] != ' ' && src[i] != '\n')
14079             {
14080                 i--;
14081                 if (len)
14082                     len--;
14083             }
14084
14085             // now append the newline and continuation sequence
14086             if (dest)
14087                 dest[len] = '\n';
14088             len++;
14089             if (dest)
14090                 strncpy(dest+len, cseq, cseq_len);
14091             len += cseq_len;
14092             line = cseq_len;
14093             clen = cseq_len;
14094             continue;
14095         }
14096
14097         if (dest)
14098             dest[len] = src[i];
14099         len++;
14100         if (!ansi)
14101             line++;
14102         if (src[i] == '\n')
14103             line = 0;
14104         if (src[i] == 'm')
14105             ansi = 0;
14106     }
14107     if (dest && appData.debugMode)
14108     {
14109         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14110             count, width, line, len, *lp);
14111         show_bytes(debugFP, src, count);
14112         fprintf(debugFP, "\ndest: ");
14113         show_bytes(debugFP, dest, len);
14114         fprintf(debugFP, "\n");
14115     }
14116     *lp = dest ? line : old_line;
14117
14118     return len;
14119 }